Index: src/Cube.mm ================================================================== --- src/Cube.mm +++ src/Cube.mm @@ -1,8 +1,10 @@ // main.cpp: initialisation & main loop #include "cube.h" + +#import "DynamicEntity.h" OF_APPLICATION_DELEGATE(Cube) VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100); VARP(minmillis, 0, 5, 1000); @@ -85,10 +87,12 @@ IRIByAppendingPathComponent:@"savegames"] createParents:true]; if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0) fatal(@"Unable to initialize SDL"); + + initPlayers(); log(@"net"); if (enet_initialize() < 0) fatal(@"Unable to initialise network module"); @@ -210,22 +214,22 @@ serverslice((int)time(NULL), 0); static float fps = 30.0f; fps = (1000.0f / curtime + fps * 50) / 51; - computeraytable(player1->o.x, player1->o.y); + computeraytable(player1.o.x, player1.o.y); readdepth(_width, _height); SDL_GL_SwapWindow(_window); extern void updatevol(); updatevol(); // cheap hack to get rid of initial sparklies, even when triple // buffering etc. if (_framesInMap++ < 5) { - player1->yaw += 5; + player1.yaw += 5; gl_drawframe(_width, _height, fps); - player1->yaw -= 5; + player1.yaw -= 5; } gl_drawframe(_width, _height, fps); SDL_Event event; ADDED src/DynamicEntity.h Index: src/DynamicEntity.h ================================================================== --- /dev/null +++ src/DynamicEntity.h @@ -0,0 +1,55 @@ +#import + +// players & monsters +@interface DynamicEntity: OFObject +@property (class, readonly, nonatomic) size_t serializedSize; + +// origin, velocity +@property (nonatomic) OFVector3D o, vel; +// used as OFVector3D in one place +@property (nonatomic) float yaw, pitch, roll; +// cubes per second, 24 for player +@property (nonatomic) float maxspeed; +// from his eyes +@property (nonatomic) bool outsidemap; +@property (nonatomic) bool inwater; +@property (nonatomic) bool onfloor, jumpnext; +@property (nonatomic) int move, strafe; +// see input code +@property (nonatomic) bool k_left, k_right, k_up, k_down; +// used for fake gravity +@property (nonatomic) int timeinair; +// bounding box size +@property (nonatomic) float radius, eyeheight, aboveeye; +@property (nonatomic) int lastupdate, plag, ping; +// sequence id for each respawn, used in damage test +@property (nonatomic) int lifesequence; +// one of CS_* below +@property (nonatomic) int state; +@property (nonatomic) int frags; +@property (nonatomic) int health, armour, armourtype, quadmillis; +@property (nonatomic) int gunselect, gunwait; +@property (nonatomic) int lastaction, lastattackgun, lastmove; +@property (nonatomic) bool attacking; +@property (readonly, nonatomic) int *ammo; +// one of M_* below, M_NONE means human +@property (nonatomic) int monsterstate; +// see monster.cpp +@property (nonatomic) int mtype; +// monster wants to kill this entity +@property (nonatomic) DynamicEntity *enemy; +// monster wants to look in this direction +@property (nonatomic) float targetyaw; +// used by physics to signal ai +@property (nonatomic) bool blocked, moving; +// millis at which transition to another monsterstate takes place +@property (nonatomic) int trigger; +// delayed attacks +@property (nonatomic) OFVector3D attacktarget; +// how many times already hit by fellow monster +@property (nonatomic) int anger; +@property (copy, nonatomic) OFString *name, *team; + +- (OFData *)dataBySerializing; +- (void)setFromSerializedData:(OFData *)data; +@end ADDED src/DynamicEntity.mm Index: src/DynamicEntity.mm ================================================================== --- /dev/null +++ src/DynamicEntity.mm @@ -0,0 +1,235 @@ +#import "DynamicEntity.h" + +#include "cube.h" + +struct dynent { + OFVector3D o, vel; + float yaw, pitch, roll; + float maxspeed; + bool outsidemap; + bool inwater; + bool onfloor, jumpnext; + int move, strafe; + bool k_left, k_right, k_up, k_down; + int timeinair; + float radius, eyeheight, aboveeye; + int lastupdate, plag, ping; + int lifesequence; + int state; + int frags; + int health, armour, armourtype, quadmillis; + int gunselect, gunwait; + int lastaction, lastattackgun, lastmove; + bool attacking; + int ammo[NUMGUNS]; + int monsterstate; + int mtype; + void *enemy; + float targetyaw; + bool blocked, moving; + int trigger; + OFVector3D attacktarget; + int anger; + char name[260], team[260]; +}; + +@implementation DynamicEntity ++ (size_t)serializedSize +{ + return sizeof(dynent); +} + +- (instancetype)init +{ + self = [super init]; + + _ammo = (int *)OFAllocZeroedMemory(NUMGUNS, sizeof(int)); + + return self; +} + +- (void)dealloc +{ + OFFreeMemory(_ammo); +} + +- (id)copy +{ + DynamicEntity *copy = [[self.class alloc] init]; + + copy->_o = _o; + copy->_vel = _vel; + copy->_yaw = _yaw; + copy->_pitch = _pitch; + copy->_roll = _roll; + copy->_maxspeed = _maxspeed; + copy->_outsidemap = _outsidemap; + copy->_inwater = _inwater; + copy->_onfloor = _onfloor; + copy->_jumpnext = _jumpnext; + copy->_move = _move; + copy->_strafe = _strafe; + copy->_k_left = _k_left; + copy->_k_right = _k_right; + copy->_k_up = _k_up; + copy->_k_down = _k_down; + copy->_timeinair = _timeinair; + copy->_radius = _radius; + copy->_eyeheight = _eyeheight; + copy->_aboveeye = _aboveeye; + copy->_lastupdate = _lastupdate; + copy->_plag = _plag; + copy->_ping = _ping; + copy->_lifesequence = _lifesequence; + copy->_state = _state; + copy->_frags = _frags; + copy->_health = _health; + copy->_armour = _armour; + copy->_armourtype = _armourtype; + copy->_quadmillis = _quadmillis; + copy->_gunselect = _gunselect; + copy->_gunwait = _gunwait; + copy->_lastaction = _lastaction; + copy->_lastattackgun = _lastattackgun; + copy->_lastmove = _lastmove; + copy->_attacking = _attacking; + + for (size_t i = 0; i < NUMGUNS; i++) + copy->_ammo[i] = _ammo[i]; + + copy->_monsterstate = _monsterstate; + copy->_mtype = _mtype; + copy->_enemy = _enemy; + copy->_targetyaw = _targetyaw; + copy->_blocked = _blocked; + copy->_moving = _moving; + copy->_trigger = _trigger; + copy->_attacktarget = _attacktarget; + copy->_anger = _anger; + + copy->_name = [_name copy]; + copy->_team = [_team copy]; + + return copy; +} + +- (OFData *)dataBySerializing +{ + // This is frighteningly *TERRIBLE*, but the format used by existing + // savegames. + dynent data = { .o = _o, + .vel = _vel, + .yaw = _yaw, + .pitch = _pitch, + .roll = _roll, + .maxspeed = _maxspeed, + .outsidemap = _outsidemap, + .inwater = _inwater, + .onfloor = _onfloor, + .jumpnext = _jumpnext, + .move = _move, + .strafe = _strafe, + .k_left = _k_left, + .k_right = _k_right, + .k_up = _k_up, + .k_down = _k_down, + .timeinair = _timeinair, + .radius = _radius, + .eyeheight = _eyeheight, + .aboveeye = _aboveeye, + .lastupdate = _lastupdate, + .plag = _plag, + .ping = _ping, + .lifesequence = _lifesequence, + .state = _state, + .frags = _frags, + .health = _health, + .armour = _armour, + .armourtype = _armourtype, + .quadmillis = _quadmillis, + .gunselect = _gunselect, + .gunwait = _gunwait, + .lastaction = _lastaction, + .lastattackgun = _lastattackgun, + .lastmove = _lastmove, + .attacking = _attacking, + .monsterstate = _monsterstate, + .mtype = _mtype, + .targetyaw = _targetyaw, + .blocked = _blocked, + .moving = _moving, + .trigger = _trigger, + .attacktarget = _attacktarget, + .anger = _anger }; + + for (int i = 0; i < NUMGUNS; i++) + data.ammo[i] = _ammo[i]; + + memcpy(data.name, _name.UTF8String, min(_name.UTF8StringLength, 259)); + memcpy(data.team, _team.UTF8String, min(_team.UTF8StringLength, 259)); + + return [OFData dataWithItems:&data count:sizeof(data)]; +} + +- (void)setFromSerializedData:(OFData *)data +{ + struct dynent d; + + if (data.count != sizeof(dynent)) + @throw [OFOutOfRangeException exception]; + + memcpy(&d, data.items, data.count); + + _o = d.o; + _vel = d.vel; + _yaw = d.yaw; + _pitch = d.pitch; + _roll = d.roll; + _maxspeed = d.maxspeed; + _outsidemap = d.outsidemap; + _inwater = d.inwater; + _onfloor = d.onfloor; + _jumpnext = d.jumpnext; + _move = d.move; + _strafe = d.strafe; + _k_left = d.k_left; + _k_right = d.k_right; + _k_up = d.k_up; + _k_down = d.k_down; + _timeinair = d.timeinair; + _radius = d.radius; + _eyeheight = d.eyeheight; + _aboveeye = d.aboveeye; + _lastupdate = d.lastupdate; + _plag = d.plag; + _ping = d.ping; + _lifesequence = d.lifesequence; + _state = d.state; + _frags = d.frags; + _health = d.health; + _armour = d.armour; + _armourtype = d.armourtype; + _quadmillis = d.quadmillis; + _gunselect = d.gunselect; + _gunwait = d.gunwait; + _lastaction = d.lastaction; + _lastattackgun = d.lastattackgun; + _lastmove = d.lastmove; + _attacking = d.attacking; + + for (int i = 0; i < NUMGUNS; i++) + _ammo[i] = d.ammo[i]; + + _monsterstate = d.monsterstate; + _mtype = d.mtype; + _targetyaw = d.targetyaw; + _blocked = d.blocked; + _moving = d.moving; + _trigger = d.trigger; + _attacktarget = d.attacktarget; + _anger = d.anger; + + _name = [[OFString alloc] initWithUTF8String:d.name]; + _team = [[OFString alloc] initWithUTF8String:d.team]; +} +@end Index: src/Projectile.h ================================================================== --- src/Projectile.h +++ src/Projectile.h @@ -1,11 +1,11 @@ #import -typedef struct dynent dynent; +@class DynamicEntity; @interface Projectile: OFObject @property (nonatomic) OFVector3D o, to; @property (nonatomic) float speed; -@property (nonatomic) dynent *owner; +@property (nonatomic) DynamicEntity *owner; @property (nonatomic) int gun; @property (nonatomic) bool inuse, local; @end Index: src/client.mm ================================================================== --- src/client.mm +++ src/client.mm @@ -1,15 +1,19 @@ // client.cpp, mostly network related client game code #include "cube.h" -ENetHost *clienthost = NULL; -int connecting = 0; -int connattempts = 0; -int disconnecting = 0; -int clientnum = -1; // our client id in the game -bool c2sinit = false; // whether we need to tell the other clients our stats +#import "DynamicEntity.h" + +static ENetHost *clienthost = NULL; +static int connecting = 0; +static int connattempts = 0; +static int disconnecting = 0; +// our client id in the game +int clientnum = -1; +// whether we need to tell the other clients our stats +bool c2sinit = false; int getclientnum() { return clientnum; @@ -57,32 +61,40 @@ } void newname(OFString *name) { - c2sinit = false; @autoreleasepool { - strn0cpy(player1->name, name.UTF8String, 16); + c2sinit = false; + + if (name.length > 16) + name = [name substringToIndex:16]; + + player1.name = name; } } COMMANDN(name, newname, ARG_1STR) void newteam(OFString *name) { - c2sinit = false; @autoreleasepool { - strn0cpy(player1->team, name.UTF8String, 5); + c2sinit = false; + + if (name.length > 5) + name = [name substringToIndex:5]; + + player1.team = name; } } COMMANDN(team, newteam, ARG_1STR) void writeclientinfo(OFStream *stream) { - [stream writeFormat:@"name \"%s\"\nteam \"%s\"\n", player1->name, - player1->team]; + [stream writeFormat:@"name \"%@\"\nteam \"%@\"\n", player1.name, + player1.team]; } void connects(OFString *servername) { @@ -135,12 +147,12 @@ connecting = 0; connattempts = 0; disconnecting = 0; clientnum = -1; c2sinit = false; - player1->lifesequence = 0; - loopv(players) zapdynent(players[i]); + player1.lifesequence = 0; + [players removeAllObjects]; localdisconnect(); if (!onlyclean) { stop(); @@ -166,11 +178,11 @@ static OFString *ctext; void toserver(OFString *text) { - conoutf(@"%s:\f %@", player1->name, text); + conoutf(@"%@:\f %@", player1.name, text); ctext = text; } void echo(OFString *text) @@ -269,12 +281,13 @@ enet_host_flush(clienthost); } else localclienttoserver((ENetPacket *)packet); } +// send update to the server void -c2sinfo(dynent *d) // send update to the server +c2sinfo(DynamicEntity *d) { @autoreleasepool { if (clientnum < 0) return; // we haven't had a welcome message from the // server yet @@ -293,30 +306,28 @@ toservermap = @""; putint(p, nextmode); } else { putint(p, SV_POS); putint(p, clientnum); - putint(p, - (int)(d->o.x * - DMF)); // quantize coordinates to 1/16th - // of a cube, between 1 and 3 bytes - putint(p, (int)(d->o.y * DMF)); - putint(p, (int)(d->o.z * DMF)); - putint(p, (int)(d->yaw * DAF)); - putint(p, (int)(d->pitch * DAF)); - putint(p, (int)(d->roll * DAF)); - putint( - p, (int)(d->vel.x * DVF)); // quantize to 1/100, - // almost always 1 byte - putint(p, (int)(d->vel.y * DVF)); - putint(p, (int)(d->vel.z * DVF)); + // quantize coordinates to 1/16th of a cube, between 1 + // and 3 bytes + putint(p, (int)(d.o.x * DMF)); + putint(p, (int)(d.o.y * DMF)); + putint(p, (int)(d.o.z * DMF)); + putint(p, (int)(d.yaw * DAF)); + putint(p, (int)(d.pitch * DAF)); + putint(p, (int)(d.roll * DAF)); + // quantize to 1/100, almost always 1 byte + putint(p, (int)(d.vel.x * DVF)); + putint(p, (int)(d.vel.y * DVF)); + putint(p, (int)(d.vel.z * DVF)); // pack rest in 1 byte: strafe:2, move:2, onfloor:1, // state:3 putint(p, - (d->strafe & 3) | ((d->move & 3) << 2) | - (((int)d->onfloor) << 4) | - ((editmode ? CS_EDITING : d->state) << 5)); + (d.strafe & 3) | ((d.move & 3) << 2) | + (((int)d.onfloor) << 4) | + ((editmode ? CS_EDITING : d.state) << 5)); if (senditemstoserver) { packet->flags = ENET_PACKET_FLAG_RELIABLE; putint(p, SV_ITEMLIST); if (!m_noitems) @@ -330,18 +341,18 @@ packet->flags = ENET_PACKET_FLAG_RELIABLE; putint(p, SV_TEXT); sendstring(ctext, p); ctext = @""; } - if (!c2sinit) // tell other clients who I am - { + // tell other clients who I am + if (!c2sinit) { packet->flags = ENET_PACKET_FLAG_RELIABLE; c2sinit = true; putint(p, SV_INITC2S); - sendstring(@(player1->name), p); - sendstring(@(player1->team), p); - putint(p, player1->lifesequence); + sendstring(player1.name, p); + sendstring(player1.team, p); + putint(p, player1.lifesequence); } for (OFData *msg in messages) { // send messages collected during the previous // frames if (*(int *)[msg itemAtIndex:1]) Index: src/clientextras.mm ================================================================== --- src/clientextras.mm +++ src/clientextras.mm @@ -1,8 +1,10 @@ // clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :) #include "cube.h" + +#import "DynamicEntity.h" // render players & monsters // very messy ad-hoc handling of animation frames, should be made more // configurable @@ -12,27 +14,28 @@ 40, 1, 162, 162, 67, 168 }; int range[] = { 6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1, 1 }; void -renderclient(dynent *d, bool team, OFString *mdlname, bool hellpig, float scale) +renderclient( + DynamicEntity *d, bool team, OFString *mdlname, bool hellpig, float scale) { int n = 3; float speed = 100.0f; - float mz = d->o.z - d->eyeheight + 1.55f * scale; + float mz = d.o.z - d.eyeheight + 1.55f * scale; int basetime = -((intptr_t)d & 0xFFF); - if (d->state == CS_DEAD) { + if (d.state == CS_DEAD) { int r; if (hellpig) { n = 2; r = range[3]; } else { n = (intptr_t)d % 3; r = range[n]; } - basetime = d->lastaction; - int t = lastmillis - d->lastaction; + basetime = d.lastaction; + int t = lastmillis - d.lastaction; if (t < 0 || t > 20000) return; if (t > (r - 1) * 100) { n += 4; if (t > (r + 10) * 100) { @@ -41,48 +44,53 @@ } } if (mz < -1000) return; // mdl = (((int)d>>6)&1)+1; - // mz = d->o.z-d->eyeheight+0.2f; + // mz = d.o.z-d.eyeheight+0.2f; // scale = 1.2f; - } else if (d->state == CS_EDITING) { + } else if (d.state == CS_EDITING) { n = 16; - } else if (d->state == CS_LAGGED) { + } else if (d.state == CS_LAGGED) { n = 17; - } else if (d->monsterstate == M_ATTACKING) { + } else if (d.monsterstate == M_ATTACKING) { n = 8; - } else if (d->monsterstate == M_PAIN) { + } else if (d.monsterstate == M_PAIN) { n = 10; - } else if ((!d->move && !d->strafe) || !d->moving) { + } else if ((!d.move && !d.strafe) || !d.moving) { n = 12; - } else if (!d->onfloor && d->timeinair > 100) { + } else if (!d.onfloor && d.timeinair > 100) { n = 18; } else { n = 14; - speed = 1200 / d->maxspeed * scale; + speed = 1200 / d.maxspeed * scale; if (hellpig) - speed = 300 / d->maxspeed; + speed = 300 / d.maxspeed; } if (hellpig) { n++; scale *= 32; mz -= 1.9f; } - rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, - d->yaw + 90, d->pitch / 2, team, scale, speed, 0, basetime); + rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d.o.x, mz, d.o.y, + d.yaw + 90, d.pitch / 2, team, scale, speed, 0, basetime); } extern int democlientnum; void renderclients() { - dynent *d; - loopv(players) if ((d = players[i]) && - (!demoplayback || i != democlientnum)) renderclient(d, - isteam(player1->team, d->team), @"monster/ogro", false, 1.0f); + size_t i = 0; + for (id player in players) { + if (player != [OFNull null] && + (!demoplayback || i != democlientnum)) + renderclient(player, + isteam(player1.team, [player team]), + @"monster/ogro", false, 1.0f); + i++; + } } // creation of scoreboard pseudo-menu bool scoreson = false; @@ -95,19 +103,19 @@ } static OFMutableArray *scoreLines; void -renderscore(dynent *d) +renderscore(DynamicEntity *d) { @autoreleasepool { - OFString *lag = [OFString stringWithFormat:@"%d", d->plag]; - OFString *name = [OFString stringWithFormat:@"(%s)", d->name]; - OFString *line = [OFString - stringWithFormat:@"%d\t%@\t%d\t%s\t%@", d->frags, - (d->state == CS_LAGGED ? @"LAG" : lag), d->ping, d->team, - (d->state == CS_DEAD ? name : @(d->name))]; + OFString *lag = [OFString stringWithFormat:@"%d", d.plag]; + OFString *name = [OFString stringWithFormat:@"(%@)", d.name]; + OFString *line = + [OFString stringWithFormat:@"%d\t%@\t%d\t%@\t%@", d.frags, + (d.state == CS_LAGGED ? @"LAG" : lag), d.ping, + d.team, (d.state == CS_DEAD ? name : d.name)]; if (scoreLines == nil) scoreLines = [[OFMutableArray alloc] init]; [scoreLines addObject:line]; @@ -116,34 +124,29 @@ } } static const int maxTeams = 4; static OFString *teamName[maxTeams]; -static int teamScore[maxTeams], teamsUsed; +static int teamScore[maxTeams]; +static size_t teamsUsed; void -addteamscore(dynent *d) +addteamscore(DynamicEntity *d) { - if (d == NULL) - return; - @autoreleasepool { - OFString *team = @(d->team); - - loopi(teamsUsed) - { - if ([teamName[i] isEqual:team]) { - teamScore[i] += d->frags; + for (size_t i = 0; i < teamsUsed; i++) { + if ([teamName[i] isEqual:d.team]) { + teamScore[i] += d.frags; return; } } if (teamsUsed == maxTeams) return; - teamName[teamsUsed] = @(d->team); - teamScore[teamsUsed++] = d->frags; + teamName[teamsUsed] = d.team; + teamScore[teamsUsed++] = d.frags; } } void renderscores() @@ -151,23 +154,25 @@ if (!scoreson) return; [scoreLines removeAllObjects]; if (!demoplayback) renderscore(player1); - loopv(players) if (players[i]) renderscore(players[i]); + for (id player in players) + if (player != [OFNull null]) + renderscore(player); sortmenu(); if (m_teammode) { teamsUsed = 0; - loopv(players) addteamscore(players[i]); + for (id player in players) + if (player != [OFNull null]) + addteamscore(player); if (!demoplayback) addteamscore(player1); OFMutableString *teamScores = [[OFMutableString alloc] init]; - loopj(teamsUsed) - { + for (size_t j = 0; j < teamsUsed; j++) [teamScores appendFormat:@"[ %@: %d ]", teamName[j], teamScore[j]]; - } menumanual(0, scoreLines.count, @""); @autoreleasepool { menumanual(0, scoreLines.count + 1, teamScores); } } Index: src/clientgame.mm ================================================================== --- src/clientgame.mm +++ src/clientgame.mm @@ -1,8 +1,10 @@ // clientgame.cpp: core game related stuff #include "cube.h" + +#import "DynamicEntity.h" int nextmode = 0; // nextmode becomes gamemode after next map load VAR(gamemode, 1, 0, 0); void @@ -12,12 +14,19 @@ } COMMAND(mode, ARG_1INT) bool intermission = false; -dynent *player1 = newdynent(); // our client -dvector players; // other clients +DynamicEntity *player1; // our client +OFMutableArray *players; // other clients + +void +initPlayers() +{ + player1 = newdynent(); + players = [[OFMutableArray alloc] init]; +} VARP(sensitivity, 0, 10, 10000); VARP(sensitivityscale, 1, 1, 10000); VARP(invmouse, 0, 0, 1); @@ -30,101 +39,100 @@ { return clientmap; } void -resetmovement(dynent *d) -{ - d->k_left = false; - d->k_right = false; - d->k_up = false; - d->k_down = false; - d->jumpnext = false; - d->strafe = 0; - d->move = 0; +resetmovement(DynamicEntity *d) +{ + d.k_left = false; + d.k_right = false; + d.k_up = false; + d.k_down = false; + d.jumpnext = false; + d.strafe = 0; + d.move = 0; } +// reset player state not persistent accross spawns void -spawnstate(dynent *d) // reset player state not persistent accross spawns +spawnstate(DynamicEntity *d) { resetmovement(d); - d->vel.x = d->vel.y = d->vel.z = 0; - d->onfloor = false; - d->timeinair = 0; - d->health = 100; - d->armour = 50; - d->armourtype = A_BLUE; - d->quadmillis = 0; - d->lastattackgun = d->gunselect = GUN_SG; - d->gunwait = 0; - d->attacking = false; - d->lastaction = 0; - loopi(NUMGUNS) d->ammo[i] = 0; - d->ammo[GUN_FIST] = 1; + d.vel = OFMakeVector3D(0, 0, 0); + d.onfloor = false; + d.timeinair = 0; + d.health = 100; + d.armour = 50; + d.armourtype = A_BLUE; + d.quadmillis = 0; + d.lastattackgun = d.gunselect = GUN_SG; + d.gunwait = 0; + d.attacking = false; + d.lastaction = 0; + loopi(NUMGUNS) d.ammo[i] = 0; + d.ammo[GUN_FIST] = 1; if (m_noitems) { - d->gunselect = GUN_RIFLE; - d->armour = 0; + d.gunselect = GUN_RIFLE; + d.armour = 0; if (m_noitemsrail) { - d->health = 1; - d->ammo[GUN_RIFLE] = 100; + d.health = 1; + d.ammo[GUN_RIFLE] = 100; } else { if (gamemode == 12) { - d->gunselect = GUN_FIST; + // eihrul's secret "instafist" mode + d.gunselect = GUN_FIST; return; - } // eihrul's secret "instafist" mode - d->health = 256; + } + d.health = 256; if (m_tarena) { int gun1 = rnd(4) + 1; - baseammo(d->gunselect = gun1); + baseammo(d.gunselect = gun1); for (;;) { int gun2 = rnd(4) + 1; if (gun1 != gun2) { baseammo(gun2); break; } } - } else if (m_arena) // insta arena - { - d->ammo[GUN_RIFLE] = 100; - } else // efficiency - { - loopi(4) baseammo(i + 1); - d->gunselect = GUN_CG; - } - d->ammo[GUN_CG] /= 2; - } - } else { - d->ammo[GUN_SG] = 5; - } -} - -dynent * + } else if (m_arena) { + // insta arena + d.ammo[GUN_RIFLE] = 100; + } else { + // efficiency + loopi(4) baseammo(i + 1); + d.gunselect = GUN_CG; + } + d.ammo[GUN_CG] /= 2; + } + } else + d.ammo[GUN_SG] = 5; +} + +DynamicEntity * newdynent() // create a new blank player or monster { - dynent *d = (dynent *)OFAllocMemory(1, sizeof(dynent)); - d->o.x = 0; - d->o.y = 0; - d->o.z = 0; - d->yaw = 270; - d->pitch = 0; - d->roll = 0; - d->maxspeed = 22; - d->outsidemap = false; - d->inwater = false; - d->radius = 1.1f; - d->eyeheight = 3.2f; - d->aboveeye = 0.7f; - d->frags = 0; - d->plag = 0; - d->ping = 0; - d->lastupdate = lastmillis; - d->enemy = NULL; - d->monsterstate = 0; - d->name[0] = d->team[0] = 0; - d->blocked = false; - d->lifesequence = 0; - d->state = CS_ALIVE; + DynamicEntity *d = [[DynamicEntity alloc] init]; + d.o = OFMakeVector3D(0, 0, 0); + d.yaw = 270; + d.pitch = 0; + d.roll = 0; + d.maxspeed = 22; + d.outsidemap = false; + d.inwater = false; + d.radius = 1.1f; + d.eyeheight = 3.2f; + d.aboveeye = 0.7f; + d.frags = 0; + d.plag = 0; + d.ping = 0; + d.lastupdate = lastmillis; + d.enemy = NULL; + d.monsterstate = 0; + d.name = d.team = @""; + d.blocked = false; + d.lifesequence = 0; + d.state = CS_ALIVE; spawnstate(d); return d; } void @@ -133,20 +141,20 @@ spawnplayer(player1); showscores(false); } void -arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) +arenacount( + DynamicEntity *d, int &alive, int &dead, OFString **lastteam, bool &oneteam) { - if (d->state != CS_DEAD) { + if (d.state != CS_DEAD) { alive++; - if (lastteam && strcmp(lastteam, d->team)) + if (![*lastteam isEqual:d.team]) oneteam = false; - lastteam = d->team; - } else { + *lastteam = d.team; + } else dead++; - } } int arenarespawnwait = 0; int arenadetectwait = 0; @@ -160,15 +168,17 @@ respawnself(); } } else if (arenadetectwait == 0 || arenadetectwait < lastmillis) { arenadetectwait = 0; int alive = 0, dead = 0; - char *lastteam = NULL; + OFString *lastteam = nil; bool oneteam = true; - loopv(players) if (players[i]) - arenacount(players[i], alive, dead, lastteam, oneteam); - arenacount(player1, alive, dead, lastteam, oneteam); + for (id player in players) + if (player != [OFNull null]) + arenacount( + player, alive, dead, &lastteam, oneteam); + arenacount(player1, alive, dead, &lastteam, oneteam); if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) { conoutf( @"arena round is over! next round in 5 seconds..."); if (alive) conoutf( @@ -175,47 +185,43 @@ @"team %s is last man standing", lastteam); else conoutf(@"everyone died!"); arenarespawnwait = lastmillis + 5000; arenadetectwait = lastmillis + 10000; - player1->roll = 0; + player1.roll = 0; } } } -void -zapdynent(dynent *&d) -{ - OFFreeMemory(d); - d = NULL; -} - extern int democlientnum; void otherplayers() { - loopv(players) if (players[i]) - { - const int lagtime = lastmillis - players[i]->lastupdate; - if (lagtime > 1000 && players[i]->state == CS_ALIVE) { - players[i]->state = CS_LAGGED; - continue; - } - if (lagtime && players[i]->state != CS_DEAD && - (!demoplayback || i != democlientnum)) - moveplayer( - players[i], 2, false); // use physics to extrapolate - // player position + size_t i = 0; + for (id player in players) { + if (player != [OFNull null]) { + const int lagtime = lastmillis - [player lastupdate]; + if (lagtime > 1000 && [player state] == CS_ALIVE) { + [player setState:CS_LAGGED]; + i++; + continue; + } + if (lagtime && [player state] != CS_DEAD && + (!demoplayback || i != democlientnum)) + // use physics to extrapolate player position + moveplayer(player, 2, false); + } + i++; } } void respawn() { - if (player1->state == CS_DEAD) { - player1->attacking = false; + if (player1.state == CS_DEAD) { + player1.attacking = false; if (m_arena) { conoutf(@"waiting for new round to start..."); return; } if (m_sp) { @@ -260,80 +266,77 @@ // information when our player moves } otherplayers(); if (!demoplayback) { monsterthink(); - if (player1->state == CS_DEAD) { - if (lastmillis - player1->lastaction < 2000) { - player1->move = player1->strafe = 0; + if (player1.state == CS_DEAD) { + if (lastmillis - player1.lastaction < 2000) { + player1.move = player1.strafe = 0; moveplayer(player1, 10, false); } else if (!m_arena && !m_sp && - lastmillis - player1->lastaction > 10000) + lastmillis - player1.lastaction > 10000) respawn(); } else if (!intermission) { moveplayer(player1, 20, true); checkitems(); } - c2sinfo(player1); // do this last, to reduce the - // effective frame lag + // do this last, to reduce the effective frame lag + c2sinfo(player1); } } lastmillis = millis; } // brute force but effective way to find a free spawn spot in the map void -entinmap(dynent *d) +entinmap(DynamicEntity *d) { loopi(100) // try max 100 times { float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance float dy = (rnd(21) - 10) / 10.0f * i; - d->o.x += dx; - d->o.y += dy; + OFVector3D old = d.o; + d.o = OFMakeVector3D(d.o.x + dx, d.o.y + dy, d.o.z); if (collide(d, true, 0, 0)) return; - d->o.x -= dx; - d->o.y -= dy; + d.o = old; } - conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d->o.x, - (int)d->o.y); + conoutf( + @"can't find entity spawn spot! (%d, %d)", (int)d.o.x, (int)d.o.y); // leave ent at original pos, possibly stuck } int spawncycle = -1; int fixspawn = 2; +// place at random spawn. also used by monsters! void -spawnplayer(dynent *d) // place at random spawn. also used by monsters! +spawnplayer(DynamicEntity *d) { int r = fixspawn-- > 0 ? 4 : rnd(10) + 1; loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1); if (spawncycle != -1) { - d->o.x = ents[spawncycle].x; - d->o.y = ents[spawncycle].y; - d->o.z = ents[spawncycle].z; - d->yaw = ents[spawncycle].attr1; - d->pitch = 0; - d->roll = 0; - } else { - d->o.x = d->o.y = (float)ssize / 2; - d->o.z = 4; - } + d.o = OFMakeVector3D( + ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z); + d.yaw = ents[spawncycle].attr1; + d.pitch = 0; + d.roll = 0; + } else + d.o = OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4); entinmap(d); spawnstate(d); - d->state = CS_ALIVE; + d.state = CS_ALIVE; } // movement input code -#define dir(name, v, d, s, os) \ - void name(bool isdown) \ - { \ - player1->s = isdown; \ - player1->v = isdown ? d : (player1->os ? -(d) : 0); \ - player1->lastmove = lastmillis; \ +#define dir(name, v, d, s, os) \ + void name(bool isdown) \ + { \ + player1.s = isdown; \ + player1.v = isdown ? d : (player1.os ? -(d) : 0); \ + player1.lastmove = lastmillis; \ } dir(backward, move, -1, k_down, k_up); dir(forward, move, 1, k_up, k_down); dir(left, strafe, 1, k_left, k_right); @@ -344,18 +347,18 @@ { if (intermission) return; if (editmode) editdrag(on); - else if (player1->attacking = on) + else if ((player1.attacking = on)) respawn(); } void jumpn(bool on) { - if (!intermission && (player1->jumpnext = on)) + if (!intermission && (player1.jumpnext = on)) respawn(); } COMMAND(backward, ARG_DOWN) COMMAND(forward, ARG_DOWN) @@ -367,114 +370,127 @@ void fixplayer1range() { const float MAXPITCH = 90.0f; - if (player1->pitch > MAXPITCH) - player1->pitch = MAXPITCH; - if (player1->pitch < -MAXPITCH) - player1->pitch = -MAXPITCH; - while (player1->yaw < 0.0f) - player1->yaw += 360.0f; - while (player1->yaw >= 360.0f) - player1->yaw -= 360.0f; + if (player1.pitch > MAXPITCH) + player1.pitch = MAXPITCH; + if (player1.pitch < -MAXPITCH) + player1.pitch = -MAXPITCH; + while (player1.yaw < 0.0f) + player1.yaw += 360.0f; + while (player1.yaw >= 360.0f) + player1.yaw -= 360.0f; } void mousemove(int dx, int dy) { - if (player1->state == CS_DEAD || intermission) + if (player1.state == CS_DEAD || intermission) return; const float SENSF = 33.0f; // try match quake sens - player1->yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale); - player1->pitch -= (dy / SENSF) * + player1.yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale); + player1.pitch -= (dy / SENSF) * (sensitivity / (float)sensitivityscale) * (invmouse ? -1 : 1); fixplayer1range(); } // damage arriving from the network, monsters, yourself, all ends up here. void -selfdamage(int damage, int actor, dynent *act) +selfdamage(int damage, int actor, DynamicEntity *act) { - if (player1->state != CS_ALIVE || editmode || intermission) + if (player1.state != CS_ALIVE || editmode || intermission) return; damageblend(damage); demoblend(damage); - int ad = damage * (player1->armourtype + 1) * 20 / - 100; // let armour absorb when possible - if (ad > player1->armour) - ad = player1->armour; - player1->armour -= ad; + // let armour absorb when possible + int ad = damage * (player1.armourtype + 1) * 20 / 100; + if (ad > player1.armour) + ad = player1.armour; + player1.armour -= ad; damage -= ad; float droll = damage / 0.5f; - player1->roll += player1->roll > 0 + player1.roll += player1.roll > 0 ? droll - : (player1->roll < 0 + : (player1.roll < 0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending // on amount of damage - if ((player1->health -= damage) <= 0) { + if ((player1.health -= damage) <= 0) { if (actor == -2) { - conoutf(@"you got killed by %s!", act->name); + conoutf(@"you got killed by %@!", act.name); } else if (actor == -1) { actor = getclientnum(); conoutf(@"you suicided!"); - addmsg(1, 2, SV_FRAGS, --player1->frags); + addmsg(1, 2, SV_FRAGS, --player1.frags); } else { - dynent *a = getclient(actor); - if (a) { - if (isteam(a->team, player1->team)) { + DynamicEntity *a = getclient(actor); + if (a != nil) { + if (isteam(a.team, player1.team)) conoutf(@"you got fragged by a " - @"teammate (%s)", - a->name); - } else { + @"teammate (%@)", + a.name); + else conoutf( - @"you got fragged by %s", a->name); - } + @"you got fragged by %@", a.name); } } showscores(true); addmsg(1, 2, SV_DIED, actor); - player1->lifesequence++; - player1->attacking = false; - player1->state = CS_DEAD; - player1->pitch = 0; - player1->roll = 60; + player1.lifesequence++; + player1.attacking = false; + player1.state = CS_DEAD; + player1.pitch = 0; + player1.roll = 60; playsound(S_DIE1 + rnd(2)); spawnstate(player1); - player1->lastaction = lastmillis; - } else { + player1.lastaction = lastmillis; + } else playsound(S_PAIN6); - } } void timeupdate(int timeremain) { if (!timeremain) { intermission = true; - player1->attacking = false; + player1.attacking = false; conoutf(@"intermission:"); conoutf(@"game has ended!"); showscores(true); } else { conoutf(@"time remaining: %d minutes", timeremain); } } -dynent * +DynamicEntity * getclient(int cn) // ensure valid entity { if (cn < 0 || cn >= MAXCLIENTS) { neterr(@"clientnum"); - return NULL; + return nil; } - while (cn >= players.length()) - players.add(NULL); - return players[cn] ? players[cn] : (players[cn] = newdynent()); + if (players == nil) + players = [[OFMutableArray alloc] init]; + while (cn >= players.count) + [players addObject:[OFNull null]]; + return (players[cn] != [OFNull null] ? players[cn] + : (players[cn] = newdynent())); +} + +void +setclient(int cn, id client) +{ + if (cn < 0 || cn >= MAXCLIENTS) + neterr(@"clientnum"); + if (players == nil) + players = [[OFMutableArray alloc] init]; + while (cn >= players.count) + [players addObject:[OFNull null]]; + players[cn] = client; } void initclient() { @@ -492,12 +508,14 @@ sleepwait = 0; monsterclear(); projreset(); spawncycle = -1; spawnplayer(player1); - player1->frags = 0; - loopv(players) if (players[i]) players[i]->frags = 0; + player1.frags = 0; + for (id player in players) + if (player != [OFNull null]) + [player setFrags:0]; resetspawns(); clientmap = name; if (editmode) toggleedit(); setvar(@"gamespeed", 100); Index: src/clients2c.mm ================================================================== --- src/clients2c.mm +++ src/clients2c.mm @@ -1,9 +1,11 @@ // client processing of the incoming network stream #include "cube.h" +#import "DynamicEntity.h" + extern int clientnum; extern bool c2sinit, senditemstoserver; extern OFString *toservermap; extern OFString *clientpassword; @@ -30,45 +32,49 @@ // update the position of other clients in the game in our world // don't care if he's in the scenery or other players, // just don't overlap with our client void -updatepos(dynent *d) -{ - const float r = player1->radius + d->radius; - const float dx = player1->o.x - d->o.x; - const float dy = player1->o.y - d->o.y; - const float dz = player1->o.z - d->o.z; - const float rz = player1->aboveeye + d->eyeheight; - const float fx = (float)fabs(dx), fy = (float)fabs(dy), - fz = (float)fabs(dz); - if (fx < r && fy < r && fz < rz && d->state != CS_DEAD) { - if (fx < fy) - d->o.y += dy < 0 ? r - fy : -(r - fy); // push aside - else - d->o.x += dx < 0 ? r - fx : -(r - fx); - } - int lagtime = lastmillis - d->lastupdate; - if (lagtime) { - d->plag = (d->plag * 5 + lagtime) / 6; - d->lastupdate = lastmillis; - } -} - -void -localservertoclient( - uchar *buf, int len) // processes any updates from the server +updatepos(DynamicEntity *d) +{ + const float r = player1.radius + d.radius; + const float dx = player1.o.x - d.o.x; + const float dy = player1.o.y - d.o.y; + const float dz = player1.o.z - d.o.z; + const float rz = player1.aboveeye + d.eyeheight; + const float fx = (float)fabs(dx), fy = (float)fabs(dy), + fz = (float)fabs(dz); + if (fx < r && fy < r && fz < rz && d.state != CS_DEAD) { + if (fx < fy) + // push aside + d.o = OFMakeVector3D(d.o.x, + d.o.y + (dy < 0 ? r - fy : -(r - fy)), d.o.z); + else + d.o = OFMakeVector3D( + d.o.x + (dx < 0 ? r - fx : -(r - fx)), d.o.y, + d.o.z); + } + int lagtime = lastmillis - d.lastupdate; + if (lagtime) { + d.plag = (d.plag * 5 + lagtime) / 6; + d.lastupdate = lastmillis; + } +} + +// processes any updates from the server +void +localservertoclient(uchar *buf, int len) { if (ENET_NET_TO_HOST_16(*(ushort *)buf) != len) neterr(@"packet length"); incomingdemodata(buf, len); uchar *end = buf + len; uchar *p = buf + 2; char text[MAXTRANS]; int cn = -1, type; - dynent *d = NULL; + DynamicEntity *d = nil; bool mapchanged = false; while (p < end) switch (type = getint(p)) { case SV_INITS2C: // welcome messsage from the server @@ -102,46 +108,46 @@ if (getint(p) == 1) conoutf(@"server is FULL, disconnecting.."); break; } - case SV_POS: // position of another client - { + case SV_POS: { + // position of another client cn = getint(p); d = getclient(cn); - if (!d) + if (d == nil) return; - d->o.x = getint(p) / DMF; - d->o.y = getint(p) / DMF; - d->o.z = getint(p) / DMF; - d->yaw = getint(p) / DAF; - d->pitch = getint(p) / DAF; - d->roll = getint(p) / DAF; - d->vel.x = getint(p) / DVF; - d->vel.y = getint(p) / DVF; - d->vel.z = getint(p) / DVF; + d.o = OFMakeVector3D( + getint(p) / DMF, getint(p) / DMF, getint(p) / DMF); + d.yaw = getint(p) / DAF; + d.pitch = getint(p) / DAF; + d.roll = getint(p) / DAF; + d.vel = OFMakeVector3D( + getint(p) / DVF, getint(p) / DVF, getint(p) / DVF); int f = getint(p); - d->strafe = (f & 3) == 3 ? -1 : f & 3; + d.strafe = (f & 3) == 3 ? -1 : f & 3; f >>= 2; - d->move = (f & 3) == 3 ? -1 : f & 3; - d->onfloor = (f >> 2) & 1; + d.move = (f & 3) == 3 ? -1 : f & 3; + d.onfloor = (f >> 2) & 1; int state = f >> 3; - if (state == CS_DEAD && d->state != CS_DEAD) - d->lastaction = lastmillis; - d->state = state; + if (state == CS_DEAD && d.state != CS_DEAD) + d.lastaction = lastmillis; + d.state = state; if (!demoplayback) updatepos(d); break; } - case SV_SOUND: - playsound(getint(p), &d->o); + case SV_SOUND: { + OFVector3D loc = d.o; + playsound(getint(p), &loc); break; + } case SV_TEXT: sgetstr(); - conoutf(@"%s:\f %s", d->name, text); + conoutf(@"%@:\f %s", d.name, text); break; case SV_MAPCHANGE: sgetstr(); @autoreleasepool { @@ -171,39 +177,39 @@ getalias(nextmapalias); // look up map in the cycle changemap(map != nil ? map : getclientmap()); break; } - case SV_INITC2S: // another client either connected or changed - // name/team - { + // another client either connected or changed name/team + case SV_INITC2S: { sgetstr(); - if (d->name[0]) { + if (d.name.length > 0) { // already connected - if (strcmp(d->name, text)) - conoutf(@"%s is now known as %s", - d->name, text); + if (![d.name isEqual:@(text)]) + conoutf(@"%@ is now known as %s", + d.name, text); } else { // new client - c2sinit = - false; // send new players my info again + + // send new players my info again + c2sinit = false; conoutf(@"connected: %s", text); } - strcpy_s(d->name, text); + d.name = @(text); sgetstr(); - strcpy_s(d->team, text); - d->lifesequence = getint(p); + d.team = @(text); + d.lifesequence = getint(p); break; } case SV_CDIS: cn = getint(p); - if (!(d = getclient(cn))) + if ((d = getclient(cn)) == nil) break; - conoutf(@"player %s disconnected", - d->name[0] ? d->name : "[incompatible client]"); - zapdynent(players[cn]); + conoutf(@"player %@ disconnected", + d.name.length ? d.name : @"[incompatible client]"); + players[cn] = [OFNull null]; break; case SV_SHOT: { int gun = getint(p); OFVector3D s, e; @@ -222,53 +228,55 @@ case SV_DAMAGE: { int target = getint(p); int damage = getint(p); int ls = getint(p); if (target == clientnum) { - if (ls == player1->lifesequence) + if (ls == player1.lifesequence) selfdamage(damage, cn, d); - } else - playsound( - S_PAIN1 + rnd(5), &getclient(target)->o); + } else { + OFVector3D loc = getclient(target).o; + playsound(S_PAIN1 + rnd(5), &loc); + } break; } case SV_DIED: { int actor = getint(p); if (actor == cn) { - conoutf(@"%s suicided", d->name); + conoutf(@"%@ suicided", d.name); } else if (actor == clientnum) { int frags; - if (isteam(player1->team, d->team)) { + if (isteam(player1.team, d.team)) { frags = -1; - conoutf(@"you fragged a teammate (%s)", - d->name); + conoutf(@"you fragged a teammate (%@)", + d.name); } else { frags = 1; - conoutf(@"you fragged %s", d->name); - } - addmsg(1, 2, SV_FRAGS, player1->frags += frags); - } else { - dynent *a = getclient(actor); - if (a) { - if (isteam(a->team, d->name)) { - conoutf(@"%s fragged his " - @"teammate (%s)", - a->name, d->name); - } else { - conoutf(@"%s fragged %s", - a->name, d->name); - } - } - } - playsound(S_DIE1 + rnd(2), &d->o); - d->lifesequence++; + conoutf(@"you fragged %@", d.name); + } + addmsg( + 1, 2, SV_FRAGS, (player1.frags += frags)); + } else { + DynamicEntity *a = getclient(actor); + if (a != nil) { + if (isteam(a.team, d.name)) + conoutf(@"%@ fragged his " + @"teammate (%@)", + a.name, d.name); + else + conoutf(@"%@ fragged %@", + a.name, d.name); + } + } + OFVector3D loc = d.o; + playsound(S_DIE1 + rnd(2), &loc); + d.lifesequence++; break; } case SV_FRAGS: - players[cn]->frags = getint(p); + [players[cn] setFrags:getint(p)]; break; case SV_ITEMPICKUP: setspawn(getint(p), false); getint(p); @@ -346,17 +354,17 @@ getint(p); break; case SV_PONG: addmsg(0, 2, SV_CLIENTPING, - player1->ping = - (player1->ping * 5 + lastmillis - getint(p)) / + player1.ping = + (player1.ping * 5 + lastmillis - getint(p)) / 6); break; case SV_CLIENTPING: - players[cn]->ping = getint(p); + [players[cn] setPing:getint(p)]; break; case SV_GAMEMODE: nextmode = getint(p); break; Index: src/cube.h ================================================================== --- src/cube.h +++ src/cube.h @@ -5,10 +5,12 @@ #define gamma gamma__ #include #undef gamma #include "tools.h" + +@class DynamicEntity; @interface Cube: OFObject @property (class, readonly, nonatomic) Cube *sharedInstance; @property (readonly, nonatomic) SDL_Window *window; @property (readonly, nonatomic) OFIRI *gameDataIRI, *userDataIRI; @@ -126,45 +128,12 @@ GUN_SLIMEBALL, GUN_BITE, NUMGUNS }; -struct dynent // players & monsters -{ - OFVector3D o, vel; // origin, velocity - float yaw, pitch, roll; // used as OFVector3D in one place - float maxspeed; // cubes per second, 24 for player - bool outsidemap; // from his eyes - bool inwater; - bool onfloor, jumpnext; - int move, strafe; - bool k_left, k_right, k_up, k_down; // see input code - int timeinair; // used for fake gravity - float radius, eyeheight, aboveeye; // bounding box size - int lastupdate, plag, ping; - int lifesequence; // sequence id for each respawn, used in damage test - int state; // one of CS_* below - int frags; - int health, armour, armourtype, quadmillis; - int gunselect, gunwait; - int lastaction, lastattackgun, lastmove; - bool attacking; - int ammo[NUMGUNS]; - int monsterstate; // one of M_* below, M_NONE means human - int mtype; // see monster.cpp - dynent *enemy; // monster wants to kill this entity - float targetyaw; // monster wants to look in this direction - bool blocked, moving; // used by physics to signal ai - int trigger; // millis at which transition to another monsterstate takes - // place - OFVector3D attacktarget; // delayed attacks - int anger; // how many times already hit by fellow monster - string name, team; -}; - -#define SAVEGAMEVERSION \ - 4 // bump if dynent/netprotocol changes or any other savegame/demo data +// bump if dynent/netprotocol changes or any other savegame/demo data +#define SAVEGAMEVERSION 4 enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off enum { M_NONE = 0, M_SEARCH, @@ -279,22 +248,21 @@ struct vertex { float u, v, x, y, z; uchar r, g, b, a; }; -typedef vector dvector; - // globals ooh naughty extern sqr *world, *wmip[]; // map data, the mips are sequential 2D arrays in memory extern header hdr; // current map header extern int sfactor, ssize; // ssize = 2^sfactor extern int cubicsize, mipsize; // cubicsize = ssize^2 -extern dynent - *player1; // special client ent that receives input and acts as camera -extern dvector players; // all the other clients (in multiplayer) +// special client ent that receives input and acts as camera +extern DynamicEntity *player1; +// all the other clients (in multiplayer) +extern OFMutableArray *players; extern bool editmode; extern vector ents; // map entities extern OFVector3D worldpos; // current target of the crosshair in the world extern int lastmillis; // last time extern int curtime; // current frame time @@ -314,34 +282,36 @@ #define PI (3.1415927f) #define PI2 (2 * PI) // simplistic vector ops #define dotprod(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) -#define vmul(u, f) \ - { \ - (u).x *= (f); \ - (u).y *= (f); \ - (u).z *= (f); \ - } -#define vdiv(u, f) \ - { \ - (u).x /= (f); \ - (u).y /= (f); \ - (u).z /= (f); \ - } -#define vadd(u, v) \ - { \ - (u).x += (v).x; \ - (u).y += (v).y; \ - (u).z += (v).z; \ - }; -#define vsub(u, v) \ - { \ - (u).x -= (v).x; \ - (u).y -= (v).y; \ - (u).z -= (v).z; \ - }; +#define vmul(u, f) \ + { \ + OFVector3D tmp_ = u; \ + float tmp2_ = f; \ + u = OFMakeVector3D( \ + tmp_.x * tmp2_, tmp_.y * tmp2_, tmp_.z * tmp2_); \ + } +#define vdiv(u, f) \ + { \ + OFVector3D tmp_ = u; \ + float tmp2_ = f; \ + u = OFMakeVector3D( \ + tmp_.x / tmp2_, tmp_.y / tmp2_, tmp_.z / tmp2_); \ + } +#define vadd(u, v) \ + { \ + OFVector3D tmp_ = u; \ + u = OFMakeVector3D( \ + tmp_.x + (v).x, tmp_.y + (v).y, tmp_.z + (v).z); \ + } +#define vsub(u, v) \ + { \ + OFVector3D tmp_ = u; \ + u = OFMakeVector3D( \ + tmp_.x - (v).x, tmp_.y - (v).y, tmp_.z - (v).z); \ + } #define vdist(d, v, e, s) \ OFVector3D v = s; \ vsub(v, e); \ float d = (float)sqrt(dotprod(v, v)); #define vreject(v, u, max) \ @@ -368,11 +338,11 @@ #define m_tarena (gamemode >= 10) #define m_teammode (gamemode & 1 && gamemode > 2) #define m_sp (gamemode < 0) #define m_dmsp (gamemode == -1) #define m_classicsp (gamemode == -2) -#define isteam(a, b) (m_teammode && strcmp(a, b) == 0) +#define isteam(a, b) (m_teammode && [a isEqual:b]) enum // function signatures for script functions, see command.cpp { ARG_1INT, ARG_2INT, Index: src/editing.mm ================================================================== --- src/editing.mm +++ src/editing.mm @@ -1,10 +1,12 @@ // editing.cpp: most map editing commands go here, entity editing commands are // in world.cpp #include "cube.h" +#import "DynamicEntity.h" + bool editmode = false; // the current selection, used by almost all editing commands // invariant: all code assumes that these are kept inside MINBORD distance of // the edge of the map @@ -50,11 +52,11 @@ VAR(editing, 0, 0, 1); void toggleedit() { - if (player1->state == CS_DEAD) + if (player1.state == CS_DEAD) return; // do not allow dead players to edit to avoid state // confusion if (!editmode && !allowedittoggle()) return; // not in most multiplayer modes if (!(editmode = !editmode)) { @@ -61,11 +63,11 @@ settagareas(); // reset triggers to allow quick playtesting entinmap(player1); // find spawn closest to current floating pos } else { resettagareas(); // clear trigger areas to allow them to be // edited - player1->health = 100; + player1.health = 100; if (m_classicsp) monsterclear(); // all monsters back at their spawns for // editing projreset(); } @@ -148,11 +150,11 @@ } void cursorupdate() // called every frame from hud { - flrceil = ((int)(player1->pitch >= 0)) * 2; + flrceil = ((int)(player1.pitch >= 0)) * 2; volatile float x = worldpos.x; // volatile needed to prevent msvc7 optimizer bug? volatile float y = worldpos.y; volatile float z = worldpos.z; @@ -164,12 +166,12 @@ return; sqr *s = S(cx, cy); // selected wall if (fabs(sheight(s, s, z) - z) > 1) { - x += x > player1->o.x ? 0.5f : -0.5f; // find right wall cube - y += y > player1->o.y ? 0.5f : -0.5f; + x += x > player1.o.x ? 0.5f : -0.5f; // find right wall cube + y += y > player1.o.y ? 0.5f : -0.5f; cx = (int)x; cy = (int)y; if (OUTBORD(cx, cy)) @@ -320,13 +322,13 @@ } } } void -editdrag(bool isdown) +editdrag(bool isDown) { - if (dragging = isdown) { + if ((dragging = isDown)) { lastx = cx; lasty = cy; lasth = ch; selset = false; tofronttex(); @@ -600,11 +602,11 @@ void newent(OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4) { EDITSEL; @autoreleasepool { - newentity(sel.x, sel.y, (int)player1->o.z, what, + newentity(sel.x, sel.y, (int)player1.o.z, what, (int)[a1 longLongValueWithBase:0], (int)[a2 longLongValueWithBase:0], (int)[a3 longLongValueWithBase:0], (int)[a4 longLongValueWithBase:0]); } Index: src/entities.mm ================================================================== --- src/entities.mm +++ src/entities.mm @@ -1,9 +1,10 @@ // entities.cpp: map entity related functions (pickup etc.) #include "cube.h" +#import "DynamicEntity.h" #import "MapModelInfo.h" vector ents; static OFString *entmdlnames[] = { @@ -120,84 +121,85 @@ }; void baseammo(int gun) { - player1->ammo[gun] = itemstats[gun - 1].add * 2; + player1.ammo[gun] = itemstats[gun - 1].add * 2; } // these two functions are called when the server acknowledges that you really // picked up the item (in multiplayer someone may grab it before you). -void -radditem(int i, int &v) +static int +radditem(int i, int v) { itemstat &is = itemstats[ents[i].type - I_SHELLS]; ents[i].spawned = false; v += is.add; if (v > is.max) v = is.max; playsoundc(is.sound); + return v; } void -realpickup(int n, dynent *d) +realpickup(int n, DynamicEntity *d) { switch (ents[n].type) { case I_SHELLS: - radditem(n, d->ammo[1]); + d.ammo[1] = radditem(n, d.ammo[1]); break; case I_BULLETS: - radditem(n, d->ammo[2]); + d.ammo[2] = radditem(n, d.ammo[2]); break; case I_ROCKETS: - radditem(n, d->ammo[3]); + d.ammo[3] = radditem(n, d.ammo[3]); break; case I_ROUNDS: - radditem(n, d->ammo[4]); + d.ammo[4] = radditem(n, d.ammo[4]); break; case I_HEALTH: - radditem(n, d->health); + d.health = radditem(n, d.health); break; case I_BOOST: - radditem(n, d->health); + d.health = radditem(n, d.health); break; case I_GREENARMOUR: - radditem(n, d->armour); - d->armourtype = A_GREEN; + d.armour = radditem(n, d.armour); + d.armourtype = A_GREEN; break; case I_YELLOWARMOUR: - radditem(n, d->armour); - d->armourtype = A_YELLOW; + d.armour = radditem(n, d.armour); + d.armourtype = A_YELLOW; break; case I_QUAD: - radditem(n, d->quadmillis); + d.quadmillis = radditem(n, d.quadmillis); conoutf(@"you got the quad!"); break; } } // these functions are called when the client touches the item void -additem(int i, int &v, int spawnsec) -{ - if (v < itemstats[ents[i].type - I_SHELLS] - .max) // don't pick up if not needed - { - addmsg(1, 3, SV_ITEMPICKUP, i, - m_classicsp ? 100000 - : spawnsec); // first ask the server for an ack - ents[i].spawned = false; // even if someone else gets it first +additem(int i, int v, int spawnsec) +{ + // don't pick up if not needed + if (v < itemstats[ents[i].type - I_SHELLS].max) { + // first ask the server for an ack even if someone else gets it + // first + addmsg(1, 3, SV_ITEMPICKUP, i, m_classicsp ? 100000 : spawnsec); + ents[i].spawned = false; } } +// also used by monsters void -teleport(int n, dynent *d) // also used by monsters +teleport(int n, DynamicEntity *d) { int e = -1, tag = ents[n].attr1, beenhere = -1; for (;;) { e = findentity(TELEDEST, e + 1); if (e == beenhere || e < 0) { @@ -205,64 +207,64 @@ return; } if (beenhere < 0) beenhere = e; if (ents[e].attr2 == tag) { - d->o.x = ents[e].x; - d->o.y = ents[e].y; - d->o.z = ents[e].z; - d->yaw = ents[e].attr1; - d->pitch = 0; - d->vel.x = d->vel.y = d->vel.z = 0; + d.o = OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z); + d.yaw = ents[e].attr1; + d.pitch = 0; + d.vel = OFMakeVector3D(0, 0, 0); entinmap(d); playsoundc(S_TELEPORT); break; } } } void -pickup(int n, dynent *d) +pickup(int n, DynamicEntity *d) { int np = 1; - loopv(players) if (players[i]) np++; - np = np < 3 ? 4 : (np > 4 ? 2 : 3); // spawn times are dependent on - // number of players + for (id player in players) + if (player != [OFNull null]) + np++; + // spawn times are dependent on number of players + np = np < 3 ? 4 : (np > 4 ? 2 : 3); int ammo = np * 2; switch (ents[n].type) { case I_SHELLS: - additem(n, d->ammo[1], ammo); + additem(n, d.ammo[1], ammo); break; case I_BULLETS: - additem(n, d->ammo[2], ammo); + additem(n, d.ammo[2], ammo); break; case I_ROCKETS: - additem(n, d->ammo[3], ammo); + additem(n, d.ammo[3], ammo); break; case I_ROUNDS: - additem(n, d->ammo[4], ammo); + additem(n, d.ammo[4], ammo); break; case I_HEALTH: - additem(n, d->health, np * 5); + additem(n, d.health, np * 5); break; case I_BOOST: - additem(n, d->health, 60); + additem(n, d.health, 60); break; case I_GREENARMOUR: // (100h/100g only absorbs 166 damage) - if (d->armourtype == A_YELLOW && d->armour > 66) + if (d.armourtype == A_YELLOW && d.armour > 66) break; - additem(n, d->armour, 20); + additem(n, d.armour, 20); break; case I_YELLOWARMOUR: - additem(n, d->armour, 20); + additem(n, d.armour, 20); break; case I_QUAD: - additem(n, d->quadmillis, 60); + additem(n, d.quadmillis, 60); break; case CARROT: ents[n].spawned = false; triggertime = lastmillis; @@ -284,12 +286,12 @@ if (lastmillis - lastjumppad < 300) break; lastjumppad = lastmillis; OFVector3D v = OFMakeVector3D((int)(char)ents[n].attr3 / 10.0f, (int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f); - player1->vel.z = 0; - vadd(player1->vel, v); + player1.vel = OFMakeVector3D(player1.vel.x, player1.vel.y, 0); + vadd(player1.vel, v); playsoundc(S_JUMPPAD); break; } } } @@ -307,22 +309,22 @@ if (!ents[i].spawned && e.type != TELEPORT && e.type != JUMPPAD) continue; if (OUTBORD(e.x, e.y)) continue; OFVector3D v = OFMakeVector3D( - e.x, e.y, (float)S(e.x, e.y)->floor + player1->eyeheight); - vdist(dist, t, player1->o, v); + e.x, e.y, (float)S(e.x, e.y)->floor + player1.eyeheight); + vdist(dist, t, player1.o, v); if (dist < (e.type == TELEPORT ? 4 : 2.5)) pickup(i, player1); } } void checkquad(int time) { - if (player1->quadmillis && (player1->quadmillis -= time) < 0) { - player1->quadmillis = 0; + if (player1.quadmillis && (player1.quadmillis -= time) < 0) { + player1.quadmillis = 0; playsoundc(S_PUPOUT); conoutf(@"quad damage is over"); } } Index: src/meson.build ================================================================== --- src/meson.build +++ src/meson.build @@ -1,10 +1,11 @@ executable('client', [ 'Alias.m', 'Command.mm', 'Cube.mm', + 'DynamicEntity.mm', 'Identifier.m', 'KeyMapping.m', 'MD2.mm', 'MapModelInfo.m', 'Menu.m', Index: src/monster.mm ================================================================== --- src/monster.mm +++ src/monster.mm @@ -1,25 +1,30 @@ // monster.cpp: implements AI for single player monsters, currently client only #include "cube.h" -dvector monsters; -int nextmonster, spawnremain, numkilled, monstertotal, mtimestart; +#import "DynamicEntity.h" + +static OFMutableArray *monsters; +static int nextmonster, spawnremain, numkilled, monstertotal, mtimestart; VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill)); -dvector & +OFArray * getmonsters() { return monsters; } +// for savegames void restoremonsterstate() { - loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++; -} // for savegames + for (DynamicEntity *monster in monsters) + if (monster.state == CS_DEAD) + numkilled++; +} #define TOTMFREQ 13 #define NUMMONSTERTYPES 8 struct monstertype // see docs for how these values modify behaviour @@ -47,44 +52,47 @@ @"a knight", @"monster/knight" }, { GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD, @"a goblin", @"monster/goblin" }, }; -dynent * +DynamicEntity * basicmonster(int type, int yaw, int state, int trigger, int move) { if (type >= NUMMONSTERTYPES) { conoutf(@"warning: unknown monster in spawn: %d", type); type = 0; } - dynent *m = newdynent(); - monstertype *t = &monstertypes[m->mtype = type]; - m->eyeheight = 2.0f; - m->aboveeye = 1.9f; - m->radius *= t->bscale / 10.0f; - m->eyeheight *= t->bscale / 10.0f; - m->aboveeye *= t->bscale / 10.0f; - m->monsterstate = state; + DynamicEntity *m = newdynent(); + monstertype *t = &monstertypes[(m.mtype = type)]; + m.eyeheight = 2.0f; + m.aboveeye = 1.9f; + m.radius *= t->bscale / 10.0f; + m.eyeheight *= t->bscale / 10.0f; + m.aboveeye *= t->bscale / 10.0f; + m.monsterstate = state; if (state != M_SLEEP) spawnplayer(m); - m->trigger = lastmillis + trigger; - m->targetyaw = m->yaw = (float)yaw; - m->move = move; - m->enemy = player1; - m->gunselect = t->gun; - m->maxspeed = (float)t->speed; - m->health = t->health; - m->armour = 0; - loopi(NUMGUNS) m->ammo[i] = 10000; - m->pitch = 0; - m->roll = 0; - m->state = CS_ALIVE; - m->anger = 0; - @autoreleasepool { - strcpy_s(m->name, t->name.UTF8String); - } - monsters.add(m); + m.trigger = lastmillis + trigger; + m.targetyaw = m.yaw = (float)yaw; + m.move = move; + m.enemy = player1; + m.gunselect = t->gun; + m.maxspeed = (float)t->speed; + m.health = t->health; + m.armour = 0; + loopi(NUMGUNS) m.ammo[i] = 10000; + m.pitch = 0; + m.roll = 0; + m.state = CS_ALIVE; + m.anger = 0; + m.name = t->name; + + if (monsters == nil) + monsters = [[OFMutableArray alloc] init]; + + [monsters addObject:m]; + return m; } void spawnmonster() // spawn a random monster according to freq distribution in DMSP @@ -97,16 +105,16 @@ } } basicmonster(type, rnd(360), M_SEARCH, 1000, 1); } +// called after map start of when toggling edit mode to reset/spawn all +// monsters to initial state void -monsterclear() // called after map start of when toggling edit mode to - // reset/spawn all monsters to initial state +monsterclear() { - loopv(monsters) free(monsters[i]); - monsters.setsize(0); + [monsters removeAllObjects]; numkilled = 0; monstertotal = 0; spawnremain = 0; if (m_dmsp) { nextmonster = mtimestart = lastmillis + 10000; @@ -113,24 +121,22 @@ monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0; } else if (m_classicsp) { mtimestart = lastmillis; loopv(ents) if (ents[i].type == MONSTER) { - dynent *m = basicmonster( + DynamicEntity *m = basicmonster( ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0); - m->o.x = ents[i].x; - m->o.y = ents[i].y; - m->o.z = ents[i].z; + m.o = OFMakeVector3D(ents[i].x, ents[i].y, ents[i].z); entinmap(m); monstertotal++; } } } +// height-correct line of sight for monster shooting/seeing bool -los(float lx, float ly, float lz, float bx, float by, float bz, - OFVector3D &v) // height-correct line of sight for monster shooting/seeing +los(float lx, float ly, float lz, float bx, float by, float bz, OFVector3D &v) { if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by)) return false; float dx = bx - lx; float dy = by - ly; @@ -162,91 +168,90 @@ } return i >= steps; } bool -enemylos(dynent *m, OFVector3D &v) +enemylos(DynamicEntity *m, OFVector3D &v) { - v = m->o; - return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y, - m->enemy->o.z, v); + v = m.o; + return los( + m.o.x, m.o.y, m.o.z, m.enemy.o.x, m.enemy.o.y, m.enemy.o.z, v); } // monster AI is sequenced using transitions: they are in a particular state // where they execute a particular behaviour until the trigger time is hit, and // then they reevaluate their situation based on the current state, the // environment etc., and transition to the next state. Transition timeframes are // parametrized by difficulty level (skill), faster transitions means quicker // decision making means tougher AI. -void -transition(dynent *m, int state, int moving, int n, - int r) // n = at skill 0, n/2 = at skill 10, r = added random factor -{ - m->monsterstate = state; - m->move = moving; - n = n * 130 / 100; - m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1); -} - -void -normalise(dynent *m, float angle) -{ - while (m->yaw < angle - 180.0f) - m->yaw += 360.0f; - while (m->yaw > angle + 180.0f) - m->yaw -= 360.0f; -} - -void -monsteraction( - dynent *m) // main AI thinking routine, called every frame for every monster -{ - if (m->enemy->state == CS_DEAD) { - m->enemy = player1; - m->anger = 0; - } - normalise(m, m->targetyaw); - if (m->targetyaw > m->yaw) // slowly turn monster towards his target - { - m->yaw += curtime * 0.5f; - if (m->targetyaw < m->yaw) - m->yaw = m->targetyaw; - } else { - m->yaw -= curtime * 0.5f; - if (m->targetyaw > m->yaw) - m->yaw = m->targetyaw; - } - - vdist(disttoenemy, vectoenemy, m->o, m->enemy->o); - m->pitch = atan2(m->enemy->o.z - m->o.z, disttoenemy) * 180 / PI; - - // special case: if we run into scenery - if (m->blocked) { - m->blocked = false; - // try to jump over obstackle (rare) - if (!rnd(20000 / monstertypes[m->mtype].speed)) - m->jumpnext = true; - // search for a way around (common) - else if (m->trigger < lastmillis && - (m->monsterstate != M_HOME || !rnd(5))) { - // patented "random walk" AI pathfinding (tm) ;) - m->targetyaw += 180 + rnd(180); +// n = at skill 0, n/2 = at skill 10, r = added random factor +void +transition(DynamicEntity *m, int state, int moving, int n, int r) +{ + m.monsterstate = state; + m.move = moving; + n = n * 130 / 100; + m.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1); +} + +void +normalise(DynamicEntity *m, float angle) +{ + while (m.yaw < angle - 180.0f) + m.yaw += 360.0f; + while (m.yaw > angle + 180.0f) + m.yaw -= 360.0f; +} + +// main AI thinking routine, called every frame for every monster +void +monsteraction(DynamicEntity *m) +{ + if (m.enemy.state == CS_DEAD) { + m.enemy = player1; + m.anger = 0; + } + normalise(m, m.targetyaw); + // slowly turn monster towards his target + if (m.targetyaw > m.yaw) { + m.yaw += curtime * 0.5f; + if (m.targetyaw < m.yaw) + m.yaw = m.targetyaw; + } else { + m.yaw -= curtime * 0.5f; + if (m.targetyaw > m.yaw) + m.yaw = m.targetyaw; + } + + vdist(disttoenemy, vectoenemy, m.o, m.enemy.o); + m.pitch = atan2(m.enemy.o.z - m.o.z, disttoenemy) * 180 / PI; + + // special case: if we run into scenery + if (m.blocked) { + m.blocked = false; + // try to jump over obstackle (rare) + if (!rnd(20000 / monstertypes[m.mtype].speed)) + m.jumpnext = true; + // search for a way around (common) + else if (m.trigger < lastmillis && + (m.monsterstate != M_HOME || !rnd(5))) { + // patented "random walk" AI pathfinding (tm) ;) + m.targetyaw += 180 + rnd(180); transition(m, M_SEARCH, 1, 400, 1000); } } float enemyyaw = - -(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y) / PI * - 180 + + -(float)atan2(m.enemy.o.x - m.o.x, m.enemy.o.y - m.o.y) / PI * 180 + 180; - switch (m->monsterstate) { + switch (m.monsterstate) { case M_PAIN: case M_ATTACKING: case M_SEARCH: - if (m->trigger < lastmillis) + if (m.trigger < lastmillis) transition(m, M_HOME, 1, 100, 200); break; case M_SLEEP: // state classic sp monster start in, wait for visual // contact @@ -253,99 +258,100 @@ { OFVector3D target; if (editmode || !enemylos(m, target)) return; // skip running physics normalise(m, enemyyaw); - float angle = (float)fabs(enemyyaw - m->yaw); + float angle = (float)fabs(enemyyaw - m.yaw); if (disttoenemy < 8 // the better the angle to the player, the // further the monster can see/hear || (disttoenemy < 16 && angle < 135) || (disttoenemy < 32 && angle < 90) || (disttoenemy < 64 && angle < 45) || angle < 10) { transition(m, M_HOME, 1, 500, 200); - playsound(S_GRUNT1 + rnd(2), &m->o); + OFVector3D loc = m.o; + playsound(S_GRUNT1 + rnd(2), &loc); } break; } - case M_AIMING: // this state is the delay between wanting to shoot and - // actually firing - if (m->trigger < lastmillis) { - m->lastaction = 0; - m->attacking = true; - shoot(m, m->attacktarget); + case M_AIMING: + // this state is the delay between wanting to shoot and actually + // firing + if (m.trigger < lastmillis) { + m.lastaction = 0; + m.attacking = true; + shoot(m, m.attacktarget); transition(m, M_ATTACKING, 0, 600, 0); } break; - case M_HOME: // monster has visual contact, heads straight for player - // and may want to shoot at any time - m->targetyaw = enemyyaw; - if (m->trigger < lastmillis) { + case M_HOME: + // monster has visual contact, heads straight for player and + // may want to shoot at any time + m.targetyaw = enemyyaw; + if (m.trigger < lastmillis) { OFVector3D target; - if (!enemylos( - m, target)) // no visual contact anymore, let - // monster get as close as possible - // then search for player - { + if (!enemylos(m, target)) { + // no visual contact anymore, let monster get + // as close as possible then search for player transition(m, M_HOME, 1, 800, 500); - } else // the closer the monster is the more likely he - // wants to shoot - { + } else { + // the closer the monster is the more likely he + // wants to shoot if (!rnd((int)disttoenemy / 3 + 1) && - m->enemy->state == - CS_ALIVE) // get ready to fire - { - m->attacktarget = target; + m.enemy.state == CS_ALIVE) { + // get ready to fire + m.attacktarget = target; transition(m, M_AIMING, 0, - monstertypes[m->mtype].lag, 10); - } else // track player some more - { + monstertypes[m.mtype].lag, 10); + } else + // track player some more transition(m, M_HOME, 1, - monstertypes[m->mtype].rate, 0); - } + monstertypes[m.mtype].rate, 0); } } break; } moveplayer(m, 1, false); // use physics to move monster } void -monsterpain(dynent *m, int damage, dynent *d) +monsterpain(DynamicEntity *m, int damage, DynamicEntity *d) { // a monster hit us - if (d->monsterstate) { + if (d.monsterstate) { // guard for RL guys shooting themselves :) if (m != d) { // don't attack straight away, first get angry - m->anger++; - int anger = - m->mtype == d->mtype ? m->anger / 2 : m->anger; - if (anger >= monstertypes[m->mtype].loyalty) + m.anger++; + int anger = m.mtype == d.mtype ? m.anger / 2 : m.anger; + if (anger >= monstertypes[m.mtype].loyalty) // monster infight if very angry - m->enemy = d; + m.enemy = d; } } else { // player hit us - m->anger = 0; - m->enemy = d; + m.anger = 0; + m.enemy = d; } // in this state monster won't attack - transition(m, M_PAIN, 0, monstertypes[m->mtype].pain, 200); - if ((m->health -= damage) <= 0) { - m->state = CS_DEAD; - m->lastaction = lastmillis; + transition(m, M_PAIN, 0, monstertypes[m.mtype].pain, 200); + if ((m.health -= damage) <= 0) { + m.state = CS_DEAD; + m.lastaction = lastmillis; numkilled++; - player1->frags = numkilled; - playsound(monstertypes[m->mtype].diesound, &m->o); + player1.frags = numkilled; + OFVector3D loc = m.o; + playsound(monstertypes[m.mtype].diesound, &loc); int remain = monstertotal - numkilled; if (remain > 0 && remain <= 5) conoutf(@"only %d monster(s) remaining", remain); - } else - playsound(monstertypes[m->mtype].painsound, &m->o); + } else { + OFVector3D loc = m.o; + playsound(monstertypes[m.mtype].painsound, &loc); + } } void endsp(bool allkilled) { @@ -368,45 +374,44 @@ } if (monstertotal && !spawnremain && numkilled == monstertotal) endsp(true); - loopv(ents) // equivalent of player entity touch, but only teleports are - // used + // equivalent of player entity touch, but only teleports are used + loopv(ents) { entity &e = ents[i]; if (e.type != TELEPORT) continue; if (OUTBORD(e.x, e.y)) continue; OFVector3D v = OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor); - loopv(monsters) - { - if (monsters[i]->state == CS_DEAD) { - if (lastmillis - monsters[i]->lastaction < - 2000) { - monsters[i]->move = 0; - moveplayer(monsters[i], 1, false); + for (DynamicEntity *monster in monsters) { + if (monster.state == CS_DEAD) { + if (lastmillis - monster.lastaction < 2000) { + monster.move = 0; + moveplayer(monster, 1, false); } } else { - v.z += monsters[i]->eyeheight; - vdist(dist, t, monsters[i]->o, v); - v.z -= monsters[i]->eyeheight; + v.z += monster.eyeheight; + vdist(dist, t, monster.o, v); + v.z -= monster.eyeheight; if (dist < 4) - teleport( - (int)(&e - &ents[0]), monsters[i]); + teleport((int)(&e - &ents[0]), monster); } } } - loopv(monsters) if (monsters[i]->state == CS_ALIVE) - monsteraction(monsters[i]); + for (DynamicEntity *monster in monsters) + if (monster.state == CS_ALIVE) + monsteraction(monster); } void monsterrender() { - loopv(monsters) renderclient(monsters[i], false, - monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5, - monstertypes[monsters[i]->mtype].mscale / 10.0f); + for (DynamicEntity *monster in monsters) + renderclient(monster, false, + monstertypes[monster.mtype].mdlname, monster.mtype == 5, + monstertypes[monster.mtype].mscale / 10.0f); } Index: src/physics.mm ================================================================== --- src/physics.mm +++ src/physics.mm @@ -4,31 +4,33 @@ // Collision detection is simplistic but very robust (uses discrete steps at // fixed fps). #include "cube.h" +#import "DynamicEntity.h" #import "MapModelInfo.h" +// collide with player or monster bool -plcollide(dynent *d, dynent *o, float &headspace, float &hi, - float &lo) // collide with player or monster +plcollide( + DynamicEntity *d, DynamicEntity *o, float &headspace, float &hi, float &lo) { - if (o->state != CS_ALIVE) + if (o.state != CS_ALIVE) return true; - const float r = o->radius + d->radius; - if (fabs(o->o.x - d->o.x) < r && fabs(o->o.y - d->o.y) < r) { - if (d->o.z - d->eyeheight < o->o.z - o->eyeheight) { - if (o->o.z - o->eyeheight < hi) - hi = o->o.z - o->eyeheight - 1; - } else if (o->o.z + o->aboveeye > lo) - lo = o->o.z + o->aboveeye + 1; - - if (fabs(o->o.z - d->o.z) < o->aboveeye + d->eyeheight) + const float r = o.radius + d.radius; + if (fabs(o.o.x - d.o.x) < r && fabs(o.o.y - d.o.y) < r) { + if (d.o.z - d.eyeheight < o.o.z - o.eyeheight) { + if (o.o.z - o.eyeheight < hi) + hi = o.o.z - o.eyeheight - 1; + } else if (o.o.z + o.aboveeye > lo) + lo = o.o.z + o.aboveeye + 1; + + if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight) return false; - if (d->monsterstate) + if (d.monsterstate) return false; // hack - headspace = d->o.z - o->o.z - o->aboveeye - d->eyeheight; + headspace = d.o.z - o.o.z - o.aboveeye - d.eyeheight; if (headspace < 0) headspace = 10; } return true; } @@ -52,25 +54,25 @@ } return stest; } void -mmcollide(dynent *d, float &hi, float &lo) // collide with a mapmodel +mmcollide(DynamicEntity *d, float &hi, float &lo) // collide with a mapmodel { loopv(ents) { entity &e = ents[i]; if (e.type != MAPMODEL) continue; MapModelInfo *mmi = getmminfo(e.attr2); if (mmi == nil || !mmi.h) continue; - const float r = mmi.rad + d->radius; - if (fabs(e.x - d->o.x) < r && fabs(e.y - d->o.y) < r) { + const float r = mmi.rad + d.radius; + if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) { float mmz = (float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3); - if (d->o.z - d->eyeheight < mmz) { + if (d.o.z - d.eyeheight < mmz) { if (mmz < hi) hi = mmz; } else if (mmz + mmi.h > lo) lo = mmz + mmi.h; } @@ -81,31 +83,30 @@ // spawn is a dirty side effect used in spawning // drop & rise are supplied by the physics below to indicate gravity/push for // current mini-timestep bool -collide(dynent *d, bool spawn, float drop, float rise) -{ - const float fx1 = - d->o.x - d->radius; // figure out integer cube rectangle this entity - // covers in map - const float fy1 = d->o.y - d->radius; - const float fx2 = d->o.x + d->radius; - const float fy2 = d->o.y + d->radius; +collide(DynamicEntity *d, bool spawn, float drop, float rise) +{ + // figure out integer cube rectangle this entity covers in map + const float fx1 = d.o.x - d.radius; + const float fy1 = d.o.y - d.radius; + const float fx2 = d.o.x + d.radius; + const float fy2 = d.o.y + d.radius; const int x1 = fast_f2nat(fx1); const int y1 = fast_f2nat(fy1); const int x2 = fast_f2nat(fx2); const int y2 = fast_f2nat(fy2); float hi = 127, lo = -128; - float minfloor = (d->monsterstate && !spawn && d->health > 100) - ? d->o.z - d->eyeheight - 4.5f - : -1000.0f; // big monsters are afraid of heights, - // unless angry :) + // big monsters are afraid of heights, unless angry :) + float minfloor = (d.monsterstate && !spawn && d.health > 100) + ? d.o.z - d.eyeheight - 4.5f + : -1000.0f; for (int x = x1; x <= x2; x++) - for (int y = y1; y <= y2; y++) // collide with map - { + for (int y = y1; y <= y2; y++) { + // collide with map if (OUTBORD(x, y)) return false; sqr *s = S(x, y); float ceil = s->ceil; float floor = s->floor; @@ -154,59 +155,66 @@ lo = floor; if (floor < minfloor) return false; } - if (hi - lo < d->eyeheight + d->aboveeye) + if (hi - lo < d.eyeheight + d.aboveeye) return false; float headspace = 10; - loopv(players) // collide with other players - { - dynent *o = players[i]; - if (!o || o == d) + for (id player in players) { + if (player == [OFNull null] || player == d) continue; - if (!plcollide(d, o, headspace, hi, lo)) + if (!plcollide(d, player, headspace, hi, lo)) return false; } if (d != player1) if (!plcollide(d, player1, headspace, hi, lo)) return false; - dvector &v = getmonsters(); // this loop can be a performance bottleneck with many monster on a slow // cpu, should replace with a blockmap but seems mostly fast enough - loopv(v) if (!vreject(d->o, v[i]->o, 7.0f) && d != v[i] && - !plcollide(d, v[i], headspace, hi, lo)) return false; + for (DynamicEntity *monster in getmonsters()) + if (!vreject(d.o, monster.o, 7.0f) && d != monster && + !plcollide(d, monster, headspace, hi, lo)) + return false; headspace -= 0.01f; mmcollide(d, hi, lo); // collide with map models if (spawn) { - d->o.z = lo + d->eyeheight; // just drop to floor (sideeffect) - d->onfloor = true; + // just drop to floor (sideeffect) + d.o = OFMakeVector3D(d.o.x, d.o.y, lo + d.eyeheight); + d.onfloor = true; } else { - const float space = d->o.z - d->eyeheight - lo; + const float space = d.o.z - d.eyeheight - lo; if (space < 0) { if (space > -0.01) - d->o.z = lo + d->eyeheight; // stick on step + // stick on step + d.o = OFMakeVector3D( + d.o.x, d.o.y, lo + d.eyeheight); else if (space > -1.26f) - d->o.z += rise; // rise thru stair + // rise thru stair + d.o = + OFMakeVector3D(d.o.x, d.o.y, d.o.z + rise); else return false; - } else { - d->o.z -= min(min(drop, space), headspace); // gravity - } + } else + // gravity + d.o = OFMakeVector3D(d.o.x, d.o.y, + d.o.z - min(min(drop, space), headspace)); - const float space2 = hi - (d->o.z + d->aboveeye); + const float space2 = hi - (d.o.z + d.aboveeye); if (space2 < 0) { if (space2 < -0.1) - return false; // hack alert! - d->o.z = hi - d->aboveeye; // glue to ceiling - d->vel.z = 0; // cancel out jumping velocity + return false; // hack alert! + // glue to ceiling + d.o = OFMakeVector3D(d.o.x, d.o.y, hi - d.aboveeye); + // cancel out jumping velocity + d.vel = OFMakeVector3D(d.vel.x, d.vel.y, 0); } - d->onfloor = d->o.z - d->eyeheight - lo < 0.001f; + d.onfloor = d.o.z - d.eyeheight - lo < 0.001f; } return true; } float @@ -235,169 +243,173 @@ // main physics routine, moves a player/monster for a curtime step // moveres indicated the physics precision (which is lower for monsters and // multiplayer prediction) local is false for multiplayer prediction void -moveplayer(dynent *pl, int moveres, bool local, int curtime) +moveplayer(DynamicEntity *pl, int moveres, bool local, int curtime) { - const bool water = hdr.waterlevel > pl->o.z - 0.5f; - const bool floating = (editmode && local) || pl->state == CS_EDITING; + const bool water = hdr.waterlevel > pl.o.z - 0.5f; + const bool floating = (editmode && local) || pl.state == CS_EDITING; OFVector3D d; // vector of direction we ideally want to move in - d.x = (float)(pl->move * cos(rad(pl->yaw - 90))); - d.y = (float)(pl->move * sin(rad(pl->yaw - 90))); + d.x = (float)(pl.move * cos(rad(pl.yaw - 90))); + d.y = (float)(pl.move * sin(rad(pl.yaw - 90))); d.z = 0; if (floating || water) { - d.x *= (float)cos(rad(pl->pitch)); - d.y *= (float)cos(rad(pl->pitch)); - d.z = (float)(pl->move * sin(rad(pl->pitch))); + d.x *= (float)cos(rad(pl.pitch)); + d.y *= (float)cos(rad(pl.pitch)); + d.z = (float)(pl.move * sin(rad(pl.pitch))); } - d.x += (float)(pl->strafe * cos(rad(pl->yaw - 180))); - d.y += (float)(pl->strafe * sin(rad(pl->yaw - 180))); + d.x += (float)(pl.strafe * cos(rad(pl.yaw - 180))); + d.y += (float)(pl.strafe * sin(rad(pl.yaw - 180))); - const float speed = - curtime / (water ? 2000.0f : 1000.0f) * pl->maxspeed; + const float speed = curtime / (water ? 2000.0f : 1000.0f) * pl.maxspeed; const float friction = - water ? 20.0f : (pl->onfloor || floating ? 6.0f : 30.0f); + water ? 20.0f : (pl.onfloor || floating ? 6.0f : 30.0f); const float fpsfric = friction / curtime * 20.0f; - vmul(pl->vel, fpsfric - 1); // slowly apply friction and direction to - // velocity, gives a smooth movement - vadd(pl->vel, d); - vdiv(pl->vel, fpsfric); - d = pl->vel; + // slowly apply friction and direction to + // velocity, gives a smooth movement + vmul(pl.vel, fpsfric - 1); + vadd(pl.vel, d); + vdiv(pl.vel, fpsfric); + d = pl.vel; vmul(d, speed); // d is now frametime based velocity vector - pl->blocked = false; - pl->moving = true; - - if (floating) // just apply velocity - { - vadd(pl->o, d); - if (pl->jumpnext) { - pl->jumpnext = false; - pl->vel.z = 2; - } - } else // apply velocity with collision - { - if (pl->onfloor || water) { - if (pl->jumpnext) { - pl->jumpnext = false; - pl->vel.z = 1.7f; // physics impulse upwards + pl.blocked = false; + pl.moving = true; + + if (floating) { + // just apply velocity + vadd(pl.o, d); + if (pl.jumpnext) { + pl.jumpnext = false; + pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2); + } + } else { + // apply velocity with collision + if (pl.onfloor || water) { + if (pl.jumpnext) { + pl.jumpnext = false; + // physics impulse upwards + pl.vel = + OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7); // dampen velocity change even harder, gives // correct water feel - if (water) { - pl->vel.x /= 8; - pl->vel.y /= 8; - } + if (water) + pl.vel = OFMakeVector3D(pl.vel.x / 8, + pl.vel.y / 8, pl.vel.z); if (local) playsoundc(S_JUMP); - else if (pl->monsterstate) - playsound(S_JUMP, &pl->o); - } else if (pl->timeinair > 800) { + else if (pl.monsterstate) { + OFVector3D loc = pl.o; + playsound(S_JUMP, &loc); + } + } else if (pl.timeinair > 800) { // if we land after long time must have been a // high jump, make thud sound if (local) playsoundc(S_LAND); - else if (pl->monsterstate) - playsound(S_LAND, &pl->o); + else if (pl.monsterstate) { + OFVector3D loc = pl.o; + playsound(S_LAND, &loc); + } } - pl->timeinair = 0; - } else { - pl->timeinair += curtime; - } + pl.timeinair = 0; + } else + pl.timeinair += curtime; const float gravity = 20; const float f = 1.0f / moveres; // incorrect, but works fine - float dropf = ((gravity - 1) + pl->timeinair / 15.0f); + float dropf = ((gravity - 1) + pl.timeinair / 15.0f); // float slowly down in water if (water) { dropf = 5; - pl->timeinair = 0; + pl.timeinair = 0; } - const float drop = dropf * curtime / gravity / 100 / - moveres; // at high fps, gravity kicks in too fast - const float rise = speed / moveres / - 1.2f; // extra smoothness when lifting up stairs + // at high fps, gravity kicks in too fast + const float drop = dropf * curtime / gravity / 100 / moveres; + // extra smoothness when lifting up stairs + const float rise = speed / moveres / 1.2f; loopi(moveres) // discrete steps collision detection & sliding { // try move forward - pl->o.x += f * d.x; - pl->o.y += f * d.y; - pl->o.z += f * d.z; + pl.o = OFMakeVector3D(pl.o.x + f * d.x, + pl.o.y + f * d.y, pl.o.z + f * d.z); if (collide(pl, false, drop, rise)) continue; // player stuck, try slide along y axis - pl->blocked = true; - pl->o.x -= f * d.x; + pl.blocked = true; + pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z); if (collide(pl, false, drop, rise)) { d.x = 0; continue; } - pl->o.x += f * d.x; // still stuck, try x axis - pl->o.y -= f * d.y; + pl.o = OFMakeVector3D( + pl.o.x + f * d.x, pl.o.y - f * d.y, pl.o.z); if (collide(pl, false, drop, rise)) { d.y = 0; continue; } - pl->o.y += f * d.y; // try just dropping down - pl->moving = false; - pl->o.x -= f * d.x; - pl->o.y -= f * d.y; + pl.moving = false; + pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z); if (collide(pl, false, drop, rise)) { d.y = d.x = 0; continue; } - pl->o.z -= f * d.z; + pl.o = OFMakeVector3D(pl.o.x, pl.o.y, pl.o.z - f * d.z); break; } } // detect wether player is outside map, used for skipping zbuffer clear // mostly - if (pl->o.x < 0 || pl->o.x >= ssize || pl->o.y < 0 || pl->o.y > ssize) { - pl->outsidemap = true; - } else { - sqr *s = S((int)pl->o.x, (int)pl->o.y); - pl->outsidemap = SOLID(s) || - pl->o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) || - pl->o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0); + if (pl.o.x < 0 || pl.o.x >= ssize || pl.o.y < 0 || pl.o.y > ssize) + pl.outsidemap = true; + else { + sqr *s = S((int)pl.o.x, (int)pl.o.y); + pl.outsidemap = SOLID(s) || + pl.o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) || + pl.o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0); } // automatically apply smooth roll when strafing - if (pl->strafe == 0) { - pl->roll = pl->roll / (1 + (float)sqrt((float)curtime) / 25); - } else { - pl->roll += pl->strafe * curtime / -30.0f; - if (pl->roll > maxroll) - pl->roll = (float)maxroll; - if (pl->roll < -maxroll) - pl->roll = (float)-maxroll; + if (pl.strafe == 0) + pl.roll = pl.roll / (1 + (float)sqrt((float)curtime) / 25); + else { + pl.roll += pl.strafe * curtime / -30.0f; + if (pl.roll > maxroll) + pl.roll = (float)maxroll; + if (pl.roll < -maxroll) + pl.roll = (float)-maxroll; } // play sounds on water transitions - if (!pl->inwater && water) { - playsound(S_SPLASH2, &pl->o); - pl->vel.z = 0; - } else if (pl->inwater && !water) - playsound(S_SPLASH1, &pl->o); - pl->inwater = water; + if (!pl.inwater && water) { + OFVector3D loc = pl.o; + playsound(S_SPLASH2, &loc); + pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 0); + } else if (pl.inwater && !water) { + OFVector3D loc = pl.o; + playsound(S_SPLASH1, &loc); + } + pl.inwater = water; } void -moveplayer(dynent *pl, int moveres, bool local) +moveplayer(DynamicEntity *pl, int moveres, bool local) { loopi(physicsrepeat) moveplayer(pl, moveres, local, i ? curtime / physicsrepeat : curtime - curtime / physicsrepeat * (physicsrepeat - 1)); } Index: src/protos.h ================================================================== --- src/protos.h +++ src/protos.h @@ -76,39 +76,40 @@ extern void addmsg(int rel, int num, int type, ...); extern bool multiplayer(); extern bool allowedittoggle(); extern void sendpackettoserv(void *packet); extern void gets2c(); -extern void c2sinfo(dynent *d); +extern void c2sinfo(DynamicEntity *d); extern void neterr(OFString *s); extern void initclientnet(); extern bool netmapstart(); extern int getclientnum(); extern void changemapserv(OFString *name, int mode); extern void writeclientinfo(OFStream *stream); // clientgame +extern void initPlayers(); extern void mousemove(int dx, int dy); extern void updateworld(int millis); extern void startmap(OFString *name); extern void changemap(OFString *name); extern void initclient(); -extern void spawnplayer(dynent *d); -extern void selfdamage(int damage, int actor, dynent *act); -extern dynent *newdynent(); +extern void spawnplayer(DynamicEntity *d); +extern void selfdamage(int damage, int actor, DynamicEntity *act); +extern DynamicEntity *newdynent(); extern OFString *getclientmap(); extern OFString *modestr(int n); -extern void zapdynent(dynent *&d); -extern dynent *getclient(int cn); +extern DynamicEntity *getclient(int cn); +extern void setclient(int cn, id client); extern void timeupdate(int timeremain); -extern void resetmovement(dynent *d); +extern void resetmovement(DynamicEntity *d); extern void fixplayer1range(); // clientextras extern void renderclients(); extern void renderclient( - dynent *d, bool team, OFString *mdlname, bool hellpig, float scale); + DynamicEntity *d, bool team, OFString *mdlname, bool hellpig, float scale); void showscores(bool on); extern void renderscores(); // world extern void setupworld(int factor); @@ -124,11 +125,11 @@ int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4); // worldlight extern void calclight(); extern void dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, - int strength, dynent *owner); + int strength, DynamicEntity *owner); extern void cleardlights(); extern block *blockcopy(block &b); extern void blockpaste(block &b); // worldrender @@ -189,17 +190,17 @@ extern void loadgamerest(); extern void incomingdemodata(uchar *buf, int len, bool extras = false); extern void demoplaybackstep(); extern void stop(); extern void stopifrecording(); -extern void demodamage(int damage, OFVector3D &o); +extern void demodamage(int damage, const OFVector3D &o); extern void demoblend(int damage); // physics -extern void moveplayer(dynent *pl, int moveres, bool local); -extern bool collide(dynent *d, bool spawn, float drop, float rise); -extern void entinmap(dynent *d); +extern void moveplayer(DynamicEntity *pl, int moveres, bool local); +extern bool collide(DynamicEntity *d, bool spawn, float drop, float rise); +extern void entinmap(DynamicEntity *d); extern void setentphysics(int mml, int mmr); extern void physicsframe(); // sound extern void playsound(int n, const OFVector3D *loc = NULL); @@ -235,13 +236,13 @@ extern void sendmaps(int n, OFString *mapname, int mapsize, uchar *mapdata); extern ENetPacket *recvmap(int n); // weapon extern void selectgun(int a = -1, int b = -1, int c = -1); -extern void shoot(dynent *d, OFVector3D &to); -extern void shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d = 0, - bool local = false); +extern void shoot(DynamicEntity *d, const OFVector3D &to); +extern void shootv(int gun, OFVector3D &from, OFVector3D &to, + DynamicEntity *d = 0, bool local = false); extern void createrays(OFVector3D &from, OFVector3D &to); extern void moveprojectiles(float time); extern void projreset(); extern OFString *playerincrosshair(); extern int reloadtime(int gun); @@ -249,23 +250,23 @@ // monster extern void monsterclear(); extern void restoremonsterstate(); extern void monsterthink(); extern void monsterrender(); -extern dvector &getmonsters(); -extern void monsterpain(dynent *m, int damage, dynent *d); +extern OFArray *getmonsters(); +extern void monsterpain(DynamicEntity *m, int damage, DynamicEntity *d); extern void endsp(bool allkilled); // entities extern void renderents(); extern void putitems(uchar *&p); extern void checkquad(int time); extern void checkitems(); -extern void realpickup(int n, dynent *d); +extern void realpickup(int n, DynamicEntity *d); extern void renderentities(); extern void resetspawns(); extern void setspawn(uint i, bool on); -extern void teleport(int n, dynent *d); +extern void teleport(int n, DynamicEntity *d); extern void baseammo(int gun); // rndmap extern void perlinarea(block &b, int scale, int seed, int psize); Index: src/renderextras.mm ================================================================== --- src/renderextras.mm +++ src/renderextras.mm @@ -1,9 +1,11 @@ // renderextras.cpp: misc gl render code and the HUD #include "cube.h" +#import "DynamicEntity.h" + void line(int x1, int y1, float z1, int x2, int y2, float z2) { glBegin(GL_POLYGON); glVertex3f((float)x1, z1, (float)y1); @@ -327,11 +329,11 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) { readmatrices(); if (editmode) { if (cursordepth == 1.0f) - worldpos = player1->o; + worldpos = player1.o; glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); cursorupdate(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -379,15 +381,15 @@ glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, 1); glBegin(GL_QUADS); glColor3ub(255, 255, 255); if (crosshairfx) { - if (player1->gunwait) + if (player1.gunwait) glColor3ub(128, 128, 128); - else if (player1->health <= 25) + else if (player1.health <= 25) glColor3ub(255, 0, 0); - else if (player1->health <= 50) + else if (player1.health <= 50) glColor3ub(255, 128, 0); } float chsize = (float)crosshairsize; glTexCoord2d(0.0, 0.0); glVertex2f(VIRTW / 2 - chsize, VIRTH / 2 - chsize); @@ -416,27 +418,26 @@ draw_textf(@"evt %d", 3200, 2600, 2, xtraverts); } glPopMatrix(); - if (player1->state == CS_ALIVE) { + if (player1.state == CS_ALIVE) { glPushMatrix(); glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1); - draw_textf(@"%d", 90, 827, 2, player1->health); - if (player1->armour) - draw_textf(@"%d", 390, 827, 2, player1->armour); - draw_textf( - @"%d", 690, 827, 2, player1->ammo[player1->gunselect]); + draw_textf(@"%d", 90, 827, 2, player1.health); + if (player1.armour) + draw_textf(@"%d", 390, 827, 2, player1.armour); + draw_textf(@"%d", 690, 827, 2, player1.ammo[player1.gunselect]); glPopMatrix(); glPushMatrix(); glOrtho(0, VIRTW, VIRTH, 0, -1, 1); glDisable(GL_BLEND); drawicon(128, 128, 20, 1650); - if (player1->armour) + if (player1.armour) drawicon( - (float)(player1->armourtype * 64), 0, 620, 1650); - int g = player1->gunselect; + (float)(player1.armourtype * 64), 0, 620, 1650); + int g = player1.gunselect; int r = 64; if (g > 2) { g -= 3; r = 128; } Index: src/rendergl.mm ================================================================== --- src/rendergl.mm +++ src/rendergl.mm @@ -1,9 +1,11 @@ // rendergl.cpp: core opengl rendering stuff #include "cube.h" +#import "DynamicEntity.h" + #ifdef DARWIN # define GL_COMBINE_EXT GL_COMBINE_ARB # define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB # define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB # define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB @@ -334,18 +336,18 @@ void transplayer() { glLoadIdentity(); - glRotated(player1->roll, 0.0, 0.0, 1.0); - glRotated(player1->pitch, -1.0, 0.0, 0.0); - glRotated(player1->yaw, 0.0, 1.0, 0.0); - - glTranslated(-player1->o.x, - (player1->state == CS_DEAD ? player1->eyeheight - 0.2f : 0) - - player1->o.z, - -player1->o.y); + glRotated(player1.roll, 0.0, 0.0, 1.0); + glRotated(player1.pitch, -1.0, 0.0, 0.0); + glRotated(player1.yaw, 0.0, 1.0, 0.0); + + glTranslated(-player1.o.x, + (player1.state == CS_DEAD ? player1.eyeheight - 0.2f : 0) - + player1.o.z, + -player1.o.y); } VARP(fov, 10, 105, 120); int xtraverts; @@ -359,19 +361,19 @@ @"hudguns/chaing", @"hudguns/rocket", @"hudguns/rifle" }; void drawhudmodel(int start, int end, float speed, int base) { - rendermodel(hudgunnames[player1->gunselect], start, end, 0, 1.0f, - player1->o.x, player1->o.z, player1->o.y, player1->yaw + 90, - player1->pitch, false, 1.0f, speed, 0, base); + rendermodel(hudgunnames[player1.gunselect], start, end, 0, 1.0f, + player1.o.x, player1.o.z, player1.o.y, player1.yaw + 90, + player1.pitch, false, 1.0f, speed, 0, base); } void drawhudgun(float fovy, float aspect, int farplane) { - if (!hudgun /*|| !player1->gunselect*/) + if (!hudgun /*|| !player1.gunselect*/) return; glEnable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); @@ -378,18 +380,16 @@ glLoadIdentity(); gluPerspective(fovy, aspect, 0.3f, farplane); glMatrixMode(GL_MODELVIEW); // glClear(GL_DEPTH_BUFFER_BIT); - int rtime = reloadtime(player1->gunselect); - if (player1->lastaction && - player1->lastattackgun == player1->gunselect && - lastmillis - player1->lastaction < rtime) { - drawhudmodel(7, 18, rtime / 18.0f, player1->lastaction); - } else { + int rtime = reloadtime(player1.gunselect); + if (player1.lastaction && player1.lastattackgun == player1.gunselect && + lastmillis - player1.lastaction < rtime) { + drawhudmodel(7, 18, rtime / 18.0f, player1.lastaction); + } else drawhudmodel(6, 1, 100, 0); - } glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(fovy, aspect, 0.15f, farplane); glMatrixMode(GL_MODELVIEW); @@ -401,11 +401,11 @@ gl_drawframe(int w, int h, float curfps) { float hf = hdr.waterlevel - 0.3f; float fovy = (float)fov * h / w; float aspect = w / (float)h; - bool underwater = player1->o.z < hf; + bool underwater = player1.o.z < hf; glFogi(GL_FOG_START, (fog + 64) / 8); glFogi(GL_FOG_END, fog); float fogc[4] = { (fogcolour >> 16) / 256.0f, ((fogcolour >> 8) & 255) / 256.0f, (fogcolour & 255) / 256.0f, @@ -418,11 +418,11 @@ aspect += (float)sin(lastmillis / 1000.0 + PI) * 0.1f; glFogi(GL_FOG_START, 0); glFogi(GL_FOG_END, (fog + 96) / 8); } - glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) | + glClear((player1.outsidemap ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); int farplane = fog * 5 / 2; @@ -439,21 +439,21 @@ resetcubes(); curvert = 0; strips.setsize(0); - render_world(player1->o.x, player1->o.y, player1->o.z, - (int)player1->yaw, (int)player1->pitch, (float)fov, w, h); + render_world(player1.o.x, player1.o.y, player1.o.z, (int)player1.yaw, + (int)player1.pitch, (float)fov, w, h); finishstrips(); setupworld(); renderstripssky(); glLoadIdentity(); - glRotated(player1->pitch, -1.0, 0.0, 0.0); - glRotated(player1->yaw, 0.0, 1.0, 0.0); + glRotated(player1.pitch, -1.0, 0.0, 0.0); + glRotated(player1.yaw, 0.0, 1.0, 0.0); glRotated(90.0, 1.0, 0.0, 0.0); glColor3f(1.0f, 1.0f, 1.0f); glDisable(GL_FOG); glDepthFunc(GL_GREATER); draw_envbox(14, fog * 4 / 3); Index: src/rendermd2.mm ================================================================== --- src/rendermd2.mm +++ src/rendermd2.mm @@ -1,9 +1,10 @@ // rendermd2.cpp: loader code adapted from a nehe tutorial #include "cube.h" +#import "DynamicEntity.h" #import "MD2.h" #import "MapModelInfo.h" static OFMutableDictionary *mdllookup = nil; static OFMutableArray *mapmodels = nil; @@ -98,11 +99,11 @@ float y, float z, float yaw, float pitch, bool teammate, float scale, float speed, int snap, int basetime) { MD2 *m = loadmodel(mdl); - if (isoccluded(player1->o.x, player1->o.y, x - rad, z - rad, rad * 2)) + if (isoccluded(player1.o.x, player1.o.y, x - rad, z - rad, rad * 2)) return; delayedload(m); int xs, ys; Index: src/renderparticles.mm ================================================================== --- src/renderparticles.mm +++ src/renderparticles.mm @@ -1,8 +1,10 @@ // renderparticles.cpp #include "cube.h" + +#import "DynamicEntity.h" const int MAXPARTICLES = 10500; const int NUMPARTCUTOFF = 20; struct particle { OFVector3D o, d; @@ -54,11 +56,11 @@ void render_particles(int time) { if (demoplayback && demotracking) { OFVector3D nom = OFMakeVector3D(0, 0, 0); - newparticle(player1->o, nom, 100000000, 8); + newparticle(player1.o, nom, 100000000, 8); } glDepthMask(GL_FALSE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); Index: src/savegamedemo.mm ================================================================== --- src/savegamedemo.mm +++ src/savegamedemo.mm @@ -1,21 +1,23 @@ // loading and saving of savegames & demos, dumps the spawn state of all // mapents, the full state of all dynents (monsters + player) #include "cube.h" +#import "DynamicEntity.h" + #ifdef OF_BIG_ENDIAN static const int islittleendian = 0; #else static const int islittleendian = 1; #endif -gzFile f = NULL; +static gzFile f = NULL; bool demorecording = false; bool demoplayback = false; bool demoloading = false; -dvector playerhistory; +static OFMutableArray *playerhistory; int democlientnum = 0; void startdemo(); void @@ -74,12 +76,11 @@ } f = NULL; demorecording = false; demoplayback = false; demoloading = false; - loopv(playerhistory) zapdynent(playerhistory[i]); - playerhistory.setsize(0); + [playerhistory removeAllObjects]; } void stopifrecording() { @@ -100,24 +101,31 @@ return; } gzwrite(f, (void *)"CUBESAVE", 8); gzputc(f, islittleendian); gzputi(SAVEGAMEVERSION); - gzputi(sizeof(dynent)); - gzwrite(f, getclientmap().UTF8String, _MAXDEFSTR); + OFData *data = [player1 dataBySerializing]; + gzputi(data.count); + char map[260] = { 0 }; + memcpy(map, getclientmap().UTF8String, + min(getclientmap().UTF8StringLength, 259)); + gzwrite(f, map, _MAXDEFSTR); gzputi(gamemode); gzputi(ents.length()); loopv(ents) gzputc(f, ents[i].spawned); - gzwrite(f, player1, sizeof(dynent)); - dvector &monsters = getmonsters(); - gzputi(monsters.length()); - loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent)); - gzputi(players.length()); - loopv(players) - { - gzput(players[i] == NULL); - gzwrite(f, players[i], sizeof(dynent)); + gzwrite(f, data.items, data.count); + OFArray *monsters = getmonsters(); + gzputi(monsters.count); + for (DynamicEntity *monster in monsters) { + data = [monster dataBySerializing]; + gzwrite(f, data.items, data.count); + } + gzputi(players.count); + for (id player in players) { + gzput(player == [OFNull null]); + data = [player dataBySerializing]; + gzwrite(f, data.items, data.count); } } } void @@ -161,11 +169,12 @@ goto out; if (gzgetc(f) != islittleendian) goto out; // not supporting save->load accross // incompatible architectures simpifies things // a LOT - if (gzgeti() != SAVEGAMEVERSION || gzgeti() != sizeof(dynent)) + if (gzgeti() != SAVEGAMEVERSION || + gzgeti() != DynamicEntity.serializedSize) goto out; string mapname; gzread(f, mapname, _MAXDEFSTR); nextmode = gzgeti(); @autoreleasepool { @@ -216,35 +225,41 @@ if (ents[i].type == CARROT && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true); } restoreserverstate(ents); - gzread(f, player1, sizeof(dynent)); - player1->lastaction = lastmillis; + OFMutableData *data = + [OFMutableData dataWithCapacity:DynamicEntity.serializedSize]; + [data increaseCountBy:DynamicEntity.serializedSize]; + gzread(f, data.mutableItems, data.count); + [player1 setFromSerializedData:data]; + player1.lastaction = lastmillis; int nmonsters = gzgeti(); - dvector &monsters = getmonsters(); - if (nmonsters != monsters.length()) + OFArray *monsters = getmonsters(); + if (nmonsters != monsters.count) return loadgameout(); - loopv(monsters) - { - gzread(f, monsters[i], sizeof(dynent)); - monsters[i]->enemy = - player1; // lazy, could save id of enemy instead - monsters[i]->lastaction = monsters[i]->trigger = lastmillis + - 500; // also lazy, but no real noticable effect on game - if (monsters[i]->state == CS_DEAD) - monsters[i]->lastaction = 0; + + for (DynamicEntity *monster in monsters) { + gzread(f, data.mutableItems, data.count); + [monster setFromSerializedData:data]; + // lazy, could save id of enemy instead + monster.enemy = player1; + // also lazy, but no real noticable effect on game + monster.lastaction = monster.trigger = lastmillis + 500; + if (monster.state == CS_DEAD) + monster.lastaction = 0; } restoremonsterstate(); int nplayers = gzgeti(); loopi(nplayers) if (!gzget()) { - dynent *d = getclient(i); + DynamicEntity *d = getclient(i); assert(d); - gzread(f, d, sizeof(dynent)); + gzread(f, data.mutableItems, data.count); + [d setFromSerializedData:data]; } conoutf(@"savegame restored"); if (demoloading) startdemo(); @@ -285,15 +300,16 @@ } } COMMAND(record, ARG_1STR) void -demodamage(int damage, OFVector3D &o) +demodamage(int damage, const OFVector3D &o) { ddamage = damage; dorig = o; } + void demoblend(int damage) { bdamage = damage; } @@ -306,19 +322,19 @@ gzputi(lastmillis - starttime); gzputi(len); gzwrite(f, buf, len); gzput(extras); if (extras) { - gzput(player1->gunselect); - gzput(player1->lastattackgun); - gzputi(player1->lastaction - starttime); - gzputi(player1->gunwait); - gzputi(player1->health); - gzputi(player1->armour); - gzput(player1->armourtype); - loopi(NUMGUNS) gzput(player1->ammo[i]); - gzput(player1->state); + gzput(player1.gunselect); + gzput(player1.lastattackgun); + gzputi(player1.lastaction - starttime); + gzputi(player1.gunwait); + gzputi(player1.health); + gzputi(player1.armour); + gzput(player1.armourtype); + loopi(NUMGUNS) gzput(player1.ammo[i]); + gzput(player1.state); gzputi(bdamage); bdamage = 0; gzputi(ddamage); if (ddamage) { gzputv(dorig); @@ -346,11 +362,11 @@ void stopreset() { conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime); stop(); - loopv(players) zapdynent(players[i]); + [players removeAllObjects]; disconnect(0, 0); } VAR(demoplaybackspeed, 10, 100, 1000); int @@ -374,51 +390,48 @@ { democlientnum = gzgeti(); demoplayback = true; starttime = lastmillis; conoutf(@"now playing demo"); - dynent *d = getclient(democlientnum); - assert(d); - *d = *player1; + setclient(democlientnum, [player1 copy]); readdemotime(); } VAR(demodelaymsec, 0, 120, 500); // spline interpolation -void -catmulrom(OFVector3D &z, OFVector3D &a, OFVector3D &b, OFVector3D &c, float s, - OFVector3D &dest) -{ - OFVector3D t1 = b, t2 = c; - - vsub(t1, z); - vmul(t1, 0.5f) vsub(t2, a); - vmul(t2, 0.5f); - - float s2 = s * s; - float s3 = s * s2; - - dest = a; - OFVector3D t = b; - - vmul(dest, 2 * s3 - 3 * s2 + 1); - vmul(t, -2 * s3 + 3 * s2); - vadd(dest, t); - vmul(t1, s3 - 2 * s2 + s); - vadd(dest, t1); - vmul(t2, s3 - s2); - vadd(dest, t2); -} - -void -fixwrap(dynent *a, dynent *b) -{ - while (b->yaw - a->yaw > 180) - a->yaw += 360; - while (b->yaw - a->yaw < -180) - a->yaw -= 360; +#define catmulrom(z, a, b, c, s, dest) \ + { \ + OFVector3D t1 = b, t2 = c; \ + \ + vsub(t1, z); \ + vmul(t1, 0.5f); \ + vsub(t2, a); \ + vmul(t2, 0.5f); \ + \ + float s2 = s * s; \ + float s3 = s * s2; \ + \ + dest = a; \ + OFVector3D t = b; \ + \ + vmul(dest, 2 * s3 - 3 * s2 + 1); \ + vmul(t, -2 * s3 + 3 * s2); \ + vadd(dest, t); \ + vmul(t1, s3 - 2 * s2 + s); \ + vadd(dest, t1); \ + vmul(t2, s3 - s2); \ + vadd(dest, t2); \ + } + +void +fixwrap(DynamicEntity *a, DynamicEntity *b) +{ + while (b.yaw - a.yaw > 180) + a.yaw += 360; + while (b.yaw - a.yaw < -180) + a.yaw -= 360; } void demoplaybackstep() { @@ -432,27 +445,27 @@ } uchar buf[MAXTRANS]; gzread(f, buf, len); localservertoclient(buf, len); // update game state - dynent *target = players[democlientnum]; + DynamicEntity *target = players[democlientnum]; assert(target); int extras; - if (extras = gzget()) // read additional client side state not - // present in normal network stream - { - target->gunselect = gzget(); - target->lastattackgun = gzget(); - target->lastaction = scaletime(gzgeti()); - target->gunwait = gzgeti(); - target->health = gzgeti(); - target->armour = gzgeti(); - target->armourtype = gzget(); - loopi(NUMGUNS) target->ammo[i] = gzget(); - target->state = gzget(); - target->lastmove = playbacktime; + // read additional client side state not present in normal + // network stream + if (extras = gzget()) { + target.gunselect = gzget(); + target.lastattackgun = gzget(); + target.lastaction = scaletime(gzgeti()); + target.gunwait = gzgeti(); + target.health = gzgeti(); + target.armour = gzgeti(); + target.armourtype = gzget(); + loopi(NUMGUNS) target.ammo[i] = gzget(); + target.state = gzget(); + target.lastmove = playbacktime; if (bdamage = gzgeti()) damageblend(bdamage); if (ddamage = gzgeti()) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); @@ -460,70 +473,91 @@ // FIXME: set more client state here } // insert latest copy of player into history if (extras && - (playerhistory.empty() || - playerhistory.last()->lastupdate != playbacktime)) { - dynent *d = newdynent(); - *d = *target; - d->lastupdate = playbacktime; - playerhistory.add(d); - if (playerhistory.length() > 20) { - zapdynent(playerhistory[0]); - playerhistory.remove(0); - } + (playerhistory.count == 0 || + playerhistory.lastObject.lastupdate != playbacktime)) { + DynamicEntity *d = [target copy]; + d.lastupdate = playbacktime; + [playerhistory addObject:d]; + if (playerhistory.count > 20) + [playerhistory removeObjectAtIndex:0]; } readdemotime(); } - if (demoplayback) { - int itime = lastmillis - demodelaymsec; - loopvrev(playerhistory) if (playerhistory[i]->lastupdate < - itime) // find 2 positions in - // history that surround - // interpolation time point - { - dynent *a = playerhistory[i]; - dynent *b = a; - if (i + 1 < playerhistory.length()) + if (!demoplayback) + return; + + int itime = lastmillis - demodelaymsec; + // find 2 positions in history that surround interpolation time point + size_t count = playerhistory.count; + for (ssize_t i = count - 1; i >= 0; i--) { + if (playerhistory[i].lastupdate < itime) { + DynamicEntity *a = playerhistory[i]; + DynamicEntity *b = a; + + if (i + 1 < playerhistory.count) b = playerhistory[i + 1]; - *player1 = *b; - if (a != b) // interpolate pos & angles - { - dynent *c = b; - if (i + 2 < playerhistory.length()) + + player1 = b; + // interpolate pos & angles + if (a != b) { + DynamicEntity *c = b; + if (i + 2 < playerhistory.count) c = playerhistory[i + 2]; - dynent *z = a; + DynamicEntity *z = a; if (i - 1 >= 0) z = playerhistory[i - 1]; - // if(a==z || b==c) printf("* %d\n", - // lastmillis); - float bf = (itime - a->lastupdate) / - (float)(b->lastupdate - a->lastupdate); + // if(a==z || b==c) + // printf("* %d\n", lastmillis); + float bf = (itime - a.lastupdate) / + (float)(b.lastupdate - a.lastupdate); fixwrap(a, player1); fixwrap(c, player1); fixwrap(z, player1); - vdist(dist, v, z->o, c->o); - if (dist < 16) // if teleport or spawn, dont't - // interpolate - { - catmulrom(z->o, a->o, b->o, c->o, bf, - player1->o); - catmulrom(*(OFVector3D *)&z->yaw, - *(OFVector3D *)&a->yaw, - *(OFVector3D *)&b->yaw, - *(OFVector3D *)&c->yaw, bf, - *(OFVector3D *)&player1->yaw); + vdist(dist, v, z.o, c.o); + // if teleport or spawn, don't interpolate + if (dist < 16) { + catmulrom( + z.o, a.o, b.o, c.o, bf, player1.o); + OFVector3D vz = OFMakeVector3D( + z.yaw, z.pitch, z.roll); + OFVector3D va = OFMakeVector3D( + a.yaw, a.pitch, a.roll); + OFVector3D vb = OFMakeVector3D( + b.yaw, b.pitch, b.roll); + OFVector3D vc = OFMakeVector3D( + c.yaw, c.pitch, c.roll); + OFVector3D vp1 = + OFMakeVector3D(player1.yaw, + player1.pitch, player1.roll); + catmulrom(vz, va, vb, vc, bf, vp1); + z.yaw = vz.x; + z.pitch = vz.y; + z.roll = vz.z; + a.yaw = va.x; + a.pitch = va.y; + a.roll = va.z; + b.yaw = vb.x; + b.pitch = vb.y; + b.roll = vb.z; + c.yaw = vc.x; + c.pitch = vc.y; + c.roll = vc.z; + player1.yaw = vp1.x; + player1.pitch = vp1.y; + player1.roll = vp1.z; } fixplayer1range(); } break; } - // if(player1->state!=CS_DEAD) showscores(false); } + // if(player1->state!=CS_DEAD) showscores(false); } void stopn() { Index: src/sound.mm ================================================================== --- src/sound.mm +++ src/sound.mm @@ -1,9 +1,11 @@ // sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on // the other platform) #include "cube.h" + +#import "DynamicEntity.h" // #ifndef _WIN32 // NOTE: fmod not being supported for the moment as it does // not allow stereo pan/vol updating during playback #define USE_MIXER // #endif @@ -171,21 +173,19 @@ static void updatechanvol(int chan, const OFVector3D *loc) { int vol = soundvol, pan = 255 / 2; if (loc) { - vdist(dist, v, *loc, player1->o); + vdist(dist, v, *loc, player1.o); vol -= (int)(dist * 3 * soundvol / 255); // simple mono distance attenuation if (stereo && (v.x != 0 || v.y != 0)) { - float yaw = -atan2(v.x, v.y) - - player1->yaw * - (PI / 180.0f); // relative angle of - // sound along X-Y axis - pan = int(255.9f * - (0.5 * sin(yaw) + 0.5f)); // range is from 0 (left) - // to 255 (right) + // relative angle of sound along X-Y axis + float yaw = + -atan2(v.x, v.y) - player1.yaw * (PI / 180.0f); + // range is from 0 (left) to 255 (right) + pan = int(255.9f * (0.5 * sin(yaw) + 0.5f)); } } vol = (vol * MAXVOL) / 255; #ifdef USE_MIXER Mix_Volume(chan, vol); Index: src/weapon.mm ================================================================== --- src/weapon.mm +++ src/weapon.mm @@ -1,9 +1,10 @@ // weapon.cpp: all shooting and effects code #include "cube.h" +#import "DynamicEntity.h" #import "Projectile.h" static const int MONSTERDAMAGEFACTOR = 4; static const int SGRAYS = 20; static const float SGSPREAD = 2; @@ -28,30 +29,30 @@ selectgun(int a, int b, int c) { if (a < -1 || b < -1 || c < -1 || a >= NUMGUNS || b >= NUMGUNS || c >= NUMGUNS) return; - int s = player1->gunselect; - if (a >= 0 && s != a && player1->ammo[a]) + int s = player1.gunselect; + if (a >= 0 && s != a && player1.ammo[a]) s = a; - else if (b >= 0 && s != b && player1->ammo[b]) + else if (b >= 0 && s != b && player1.ammo[b]) s = b; - else if (c >= 0 && s != c && player1->ammo[c]) + else if (c >= 0 && s != c && player1.ammo[c]) s = c; - else if (s != GUN_RL && player1->ammo[GUN_RL]) + else if (s != GUN_RL && player1.ammo[GUN_RL]) s = GUN_RL; - else if (s != GUN_CG && player1->ammo[GUN_CG]) + else if (s != GUN_CG && player1.ammo[GUN_CG]) s = GUN_CG; - else if (s != GUN_SG && player1->ammo[GUN_SG]) + else if (s != GUN_SG && player1.ammo[GUN_SG]) s = GUN_SG; - else if (s != GUN_RIFLE && player1->ammo[GUN_RIFLE]) + else if (s != GUN_RIFLE && player1.ammo[GUN_RIFLE]) s = GUN_RIFLE; else s = GUN_FIST; - if (s != player1->gunselect) + if (s != player1.gunselect) playsoundc(S_WEAPLOAD); - player1->gunselect = s; + player1.gunselect = s; // conoutf(@"%@ selected", (int)guns[s].name); } int reloadtime(int gun) @@ -83,13 +84,13 @@ } } // if lineseg hits entity bounding box bool -intersect(dynent *d, const OFVector3D &from, const OFVector3D &to) +intersect(DynamicEntity *d, const OFVector3D &from, const OFVector3D &to) { - OFVector3D v = to, w = d->o; + OFVector3D v = to, w = d.o; const OFVector3D *p; vsub(v, from); vsub(w, from); float c1 = dotprod(w, v); @@ -105,27 +106,27 @@ vadd(v, from); p = &v; } } - return (p->x <= d->o.x + d->radius && p->x >= d->o.x - d->radius && - p->y <= d->o.y + d->radius && p->y >= d->o.y - d->radius && - p->z <= d->o.z + d->aboveeye && p->z >= d->o.z - d->eyeheight); + return (p->x <= d.o.x + d.radius && p->x >= d.o.x - d.radius && + p->y <= d.o.y + d.radius && p->y >= d.o.y - d.radius && + p->z <= d.o.z + d.aboveeye && p->z >= d.o.z - d.eyeheight); } OFString * playerincrosshair() { if (demoplayback) return NULL; - loopv(players) - { - dynent *o = players[i]; - if (!o) + + for (id player in players) { + if (player == [OFNull null]) continue; - if (intersect(o, player1->o, worldpos)) - return @(o->name); + + if (intersect(player, player1.o, worldpos)) + return [player name]; } return nil; } @@ -139,11 +140,11 @@ projs[i].inuse = false; } void newprojectile(OFVector3D &from, OFVector3D &to, float speed, bool local, - dynent *owner, int gun) + DynamicEntity *owner, int gun) { for (size_t i = 0; i < MAXPROJ; i++) { Projectile *p = projs[i]; if (p.inuse) @@ -159,41 +160,43 @@ return; } } void -hit(int target, int damage, dynent *d, dynent *at) +hit(int target, int damage, DynamicEntity *d, DynamicEntity *at) { if (d == player1) selfdamage(damage, at == player1 ? -1 : -2, at); - else if (d->monsterstate) + else if (d.monsterstate) monsterpain(d, damage, at); else { - addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); - playsound(S_PAIN1 + rnd(5), &d->o); + addmsg(1, 4, SV_DAMAGE, target, damage, d.lifesequence); + OFVector3D loc = d.o; + playsound(S_PAIN1 + rnd(5), &loc); } - particle_splash(3, damage, 1000, d->o); - demodamage(damage, d->o); + particle_splash(3, damage, 1000, d.o); + demodamage(damage, d.o); } const float RL_RADIUS = 5; const float RL_DAMRAD = 7; // hack static void -radialeffect(dynent *o, const OFVector3D &v, int cn, int qdam, dynent *at) +radialeffect( + DynamicEntity *o, const OFVector3D &v, int cn, int qdam, DynamicEntity *at) { - if (o->state != CS_ALIVE) + if (o.state != CS_ALIVE) return; - vdist(dist, temp, v, o->o); + vdist(dist, temp, v, o.o); dist -= 2; // account for eye distance imprecision if (dist < RL_DAMRAD) { if (dist < 0) dist = 0; int damage = (int)(qdam * (1 - (dist / RL_DAMRAD))); hit(cn, damage, o, at); vmul(temp, (RL_DAMRAD - dist) * damage / 800); - vadd(o->vel, temp); + vadd(o.vel, temp); } } void splash(Projectile *p, const OFVector3D &v, const OFVector3D &vold, @@ -209,29 +212,36 @@ newsphere(v, RL_RADIUS, 0); dodynlight(vold, v, 0, 0, p.owner); if (!p.local) return; radialeffect(player1, v, -1, qdam, p.owner); - loopv(players) - { - if (i == notthisplayer) - continue; - dynent *o = players[i]; - if (!o) - continue; - radialeffect(o, v, i, qdam, p.owner); - } - dvector &mv = getmonsters(); - loopv(mv) if (i != notthismonster) - radialeffect(mv[i], v, i, qdam, p.owner); + + size_t i = 0; + for (id player in players) { + if (i == notthisplayer) { + i++; + continue; + } + + if (player != [OFNull null]) + radialeffect(player, v, i, qdam, p.owner); + + i++; + } + + i = 0; + for (DynamicEntity *monster in getmonsters()) + if (i != notthismonster) + radialeffect(monster, v, i, qdam, p.owner); } } inline void -projdamage(dynent *o, Projectile *p, OFVector3D &v, int i, int im, int qdam) +projdamage( + DynamicEntity *o, Projectile *p, OFVector3D &v, int i, int im, int qdam) { - if (o->state != CS_ALIVE) + if (o.state != CS_ALIVE) return; if (intersect(o, p.o, v)) { splash(p, v, p.o, i, im, qdam); hit(i, qdam, o, p.owner); } @@ -244,33 +254,31 @@ Projectile *p = projs[i]; if (!p.inuse) continue; - int qdam = guns[p.gun].damage * (p.owner->quadmillis ? 4 : 1); - if (p.owner->monsterstate) + int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1); + if (p.owner.monsterstate) qdam /= MONSTERDAMAGEFACTOR; vdist(dist, v, p.o, p.to); float dtime = dist * 1000 / p.speed; if (time > dtime) dtime = time; vmul(v, time / dtime); vadd(v, p.o) if (p.local) { - loopv(players) - { - dynent *o = players[i]; - if (!o) - continue; - projdamage(o, p, v, i, -1, qdam); - } + for (id player in players) + if (player != [OFNull null]) + projdamage(player, p, v, i, -1, qdam); + if (p.owner != player1) projdamage(player1, p, v, -1, -1, qdam); - dvector &mv = getmonsters(); - loopv(mv) if (!vreject(mv[i]->o, v, 10.0f) && - mv[i] != p.owner) - projdamage(mv[i], p, v, -1, i, qdam); + + for (DynamicEntity *monster in getmonsters()) + if (!vreject(monster.o, v, 10.0f) && + monster != p.owner) + projdamage(monster, p, v, -1, i, qdam); } if (p.inuse) { if (time == dtime) splash(p, v, p.o, -1, -1, qdam); else { @@ -286,15 +294,16 @@ } p.o = v; } } +// create visual effect from a shot void -shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d, - bool local) // create visual effect from a shot +shootv(int gun, OFVector3D &from, OFVector3D &to, DynamicEntity *d, bool local) { - playsound(guns[gun].sound, d == player1 ? NULL : &d->o); + OFVector3D loc = d.o; + playsound(guns[gun].sound, d == player1 ? NULL : &loc); int pspeed = 25; switch (gun) { case GUN_FIST: break; @@ -311,11 +320,11 @@ case GUN_RL: case GUN_FIREBALL: case GUN_ICEBALL: case GUN_SLIMEBALL: pspeed = guns[gun].projspeed; - if (d->monsterstate) + if (d.monsterstate) pspeed /= 2; newprojectile(from, to, (float)pspeed, local, d, gun); break; case GUN_RIFLE: @@ -324,98 +333,99 @@ break; } } void -hitpush(int target, int damage, dynent *d, dynent *at, OFVector3D &from, - OFVector3D &to) +hitpush(int target, int damage, DynamicEntity *d, DynamicEntity *at, + OFVector3D &from, OFVector3D &to) { hit(target, damage, d, at); vdist(dist, v, from, to); vmul(v, damage / dist / 50); - vadd(d->vel, v); + vadd(d.vel, v); } void -raydamage(dynent *o, OFVector3D &from, OFVector3D &to, dynent *d, int i) +raydamage( + DynamicEntity *o, OFVector3D &from, OFVector3D &to, DynamicEntity *d, int i) { - if (o->state != CS_ALIVE) + if (o.state != CS_ALIVE) return; - int qdam = guns[d->gunselect].damage; - if (d->quadmillis) + int qdam = guns[d.gunselect].damage; + if (d.quadmillis) qdam *= 4; - if (d->monsterstate) + if (d.monsterstate) qdam /= MONSTERDAMAGEFACTOR; - if (d->gunselect == GUN_SG) { + if (d.gunselect == GUN_SG) { int damage = 0; loop(r, SGRAYS) if (intersect(o, from, sg[r])) damage += qdam; if (damage) hitpush(i, damage, o, d, from, to); } else if (intersect(o, from, to)) hitpush(i, qdam, o, d, from, to); } void -shoot(dynent *d, OFVector3D &targ) -{ - int attacktime = lastmillis - d->lastaction; - if (attacktime < d->gunwait) - return; - d->gunwait = 0; - if (!d->attacking) - return; - d->lastaction = lastmillis; - d->lastattackgun = d->gunselect; - if (!d->ammo[d->gunselect]) { - playsoundc(S_NOAMMO); - d->gunwait = 250; - d->lastattackgun = -1; - return; - } - if (d->gunselect) - d->ammo[d->gunselect]--; - OFVector3D from = d->o; +shoot(DynamicEntity *d, const OFVector3D &targ) +{ + int attacktime = lastmillis - d.lastaction; + if (attacktime < d.gunwait) + return; + d.gunwait = 0; + if (!d.attacking) + return; + d.lastaction = lastmillis; + d.lastattackgun = d.gunselect; + if (!d.ammo[d.gunselect]) { + playsoundc(S_NOAMMO); + d.gunwait = 250; + d.lastattackgun = -1; + return; + } + if (d.gunselect) + d.ammo[d.gunselect]--; + OFVector3D from = d.o; OFVector3D to = targ; from.z -= 0.2f; // below eye vdist(dist, unitv, from, to); vdiv(unitv, dist); OFVector3D kickback = unitv; - vmul(kickback, guns[d->gunselect].kickamount * -0.01f); - vadd(d->vel, kickback); - if (d->pitch < 80.0f) - d->pitch += guns[d->gunselect].kickamount * 0.05f; + vmul(kickback, guns[d.gunselect].kickamount * -0.01f); + vadd(d.vel, kickback); + if (d.pitch < 80.0f) + d.pitch += guns[d.gunselect].kickamount * 0.05f; - if (d->gunselect == GUN_FIST || d->gunselect == GUN_BITE) { + if (d.gunselect == GUN_FIST || d.gunselect == GUN_BITE) { vmul(unitv, 3); // punch range to = from; vadd(to, unitv); } - if (d->gunselect == GUN_SG) + if (d.gunselect == GUN_SG) createrays(from, to); - if (d->quadmillis && attacktime > 200) + if (d.quadmillis && attacktime > 200) playsoundc(S_ITEMPUP); - shootv(d->gunselect, from, to, d, true); - if (!d->monsterstate) - addmsg(1, 8, SV_SHOT, d->gunselect, (int)(from.x * DMF), + shootv(d.gunselect, from, to, d, true); + if (!d.monsterstate) + addmsg(1, 8, SV_SHOT, d.gunselect, (int)(from.x * DMF), (int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF), (int)(to.y * DMF), (int)(to.z * DMF)); - d->gunwait = guns[d->gunselect].attackdelay; + d.gunwait = guns[d.gunselect].attackdelay; - if (guns[d->gunselect].projspeed) + if (guns[d.gunselect].projspeed) return; - loopv(players) - { - dynent *o = players[i]; - if (!o) - continue; - raydamage(o, from, to, d, i); + size_t i = 0; + for (id player in players) { + if (player != [OFNull null]) + raydamage(player, from, to, d, i); + i++; } - dvector &v = getmonsters(); - loopv(v) if (v[i] != d) raydamage(v[i], from, to, d, -2); + for (DynamicEntity *monster in getmonsters()) + if (monster != d) + raydamage(monster, from, to, d, -2); - if (d->monsterstate) + if (d.monsterstate) raydamage(player1, from, to, d, -1); } Index: src/world.mm ================================================================== --- src/world.mm +++ src/world.mm @@ -1,19 +1,22 @@ // world.cpp: core map management stuff #include "cube.h" + +#import "DynamicEntity.h" extern OFString *entnames[]; // lookup from map entities above to strings sqr *world = NULL; int sfactor, ssize, cubicsize, mipsize; header hdr; +// set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes +// according to type void -settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then - // reset ALL tagged cubes according to type +settag(int tag, int type) { int maxx = 0, maxy = 0, minx = ssize, miny = ssize; loop(x, ssize) loop(y, ssize) { sqr *s = S(x, y); @@ -261,11 +264,11 @@ { entity &e = ents[i]; if (e.type == NOTUSED) continue; OFVector3D v = OFMakeVector3D(e.x, e.y, e.z); - vdist(dist, t, player1->o, v); + vdist(dist, t, player1.o, v); if (dist < bdist) { best = i; bdist = dist; } } @@ -343,11 +346,11 @@ e.attr3 = e.attr2; case MONSTER: case TELEDEST: e.attr2 = (uchar)e.attr1; case PLAYERSTART: - e.attr1 = (int)player1->yaw; + e.attr1 = (int)player1.yaw; break; } addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4); ents.add(*((entity *)&e)); // unsafe! Index: src/worldlight.mm ================================================================== --- src/worldlight.mm +++ src/worldlight.mm @@ -1,8 +1,10 @@ // worldlight.cpp #include "cube.h" + +#import "DynamicEntity.h" extern bool hasoverbright; VAR(lightscale, 1, 4, 100); @@ -199,15 +201,15 @@ } } void dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, int strength, - dynent *owner) + DynamicEntity *owner) { if (!reach) reach = dynlight; - if (owner->monsterstate) + if (owner.monsterstate) reach = reach / 2; if (!reach) return; if (v.x < 0 || v.y < 0 || v.x > ssize || v.y > ssize) return; Index: src/worldocull.mm ================================================================== --- src/worldocull.mm +++ src/worldocull.mm @@ -1,9 +1,11 @@ // worldocull.cpp: occlusion map and occlusion test #include "cube.h" +#import "DynamicEntity.h" + #define NUMRAYS 512 float rdist[NUMRAYS]; bool ocull = true; float odist = 256; @@ -24,14 +26,14 @@ if (!ocull) return; odist = getvar(@"fog") * 1.5f; - float apitch = (float)fabs(player1->pitch); + float apitch = (float)fabs(player1.pitch); float af = getvar(@"fov") / 2 + apitch / 1.5f + 3; - float byaw = (player1->yaw - 90 + af) / 360 * PI2; - float syaw = (player1->yaw - 90 - af) / 360 * PI2; + float byaw = (player1.yaw - 90 + af) / 360 * PI2; + float syaw = (player1.yaw - 90 - af) / 360 * PI2; loopi(NUMRAYS) { float angle = i * PI2 / NUMRAYS; if ((apitch > 45 // must be bigger if fov>120 Index: src/worldrender.mm ================================================================== --- src/worldrender.mm +++ src/worldrender.mm @@ -2,10 +2,12 @@ // determines what has to be rendered and how (depending on neighbouring cubes), // then calls functions in rendercubes.cpp #include "cube.h" +#import "DynamicEntity.h" + void render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, sqr *d2, bool topleft) { if (SOLID(o) || o->type == SEMISOLID) { @@ -133,21 +135,21 @@ float fsize = (float)(1 << mip); for (int ox = x; ox < xs; ox++) { // first collect occlusion information for this block for (int oy = y; oy < ys; oy++) { SWS(w, ox, oy, sz)->occluded = - isoccluded(player1->o.x, player1->o.y, + isoccluded(player1.o.x, player1.o.y, (float)(ox << mip), (float)(oy << mip), fsize); } } int pvx = (int)vx >> mip; int pvy = (int)vy >> mip; if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) { // SWS(w,vxx,vyy,sz)->occluded = 0; - SWS(w, pvx, pvy, sz)->occluded = - 0; // player cell never occluded + // player cell never occluded + SWS(w, pvx, pvy, sz)->occluded = 0; } #define df(x) s->floor - (x->vdelta / 4.0f) #define dc(x) s->ceil + (x->vdelta / 4.0f)