Convert dynent to a class

FossilOrigin-Name: d2b3ff790fee10ef4150cdb17f1c979e7001a420b2629b153b4dbf4c0b489704
This commit is contained in:
Jonathan Schleifer 2025-03-09 18:57:42 +00:00
parent 410e244ed6
commit 90fc249052
26 changed files with 1447 additions and 1061 deletions

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
OF_APPLICATION_DELEGATE(Cube) OF_APPLICATION_DELEGATE(Cube)
VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100); VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100);
@ -88,6 +90,8 @@ VARP(minmillis, 0, 5, 1000);
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0) if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0)
fatal(@"Unable to initialize SDL"); fatal(@"Unable to initialize SDL");
initPlayers();
log(@"net"); log(@"net");
if (enet_initialize() < 0) if (enet_initialize() < 0)
fatal(@"Unable to initialise network module"); fatal(@"Unable to initialise network module");
@ -212,7 +216,7 @@ VARP(minmillis, 0, 5, 1000);
static float fps = 30.0f; static float fps = 30.0f;
fps = (1000.0f / curtime + fps * 50) / 51; fps = (1000.0f / curtime + fps * 50) / 51;
computeraytable(player1->o.x, player1->o.y); computeraytable(player1.o.x, player1.o.y);
readdepth(_width, _height); readdepth(_width, _height);
SDL_GL_SwapWindow(_window); SDL_GL_SwapWindow(_window);
extern void updatevol(); extern void updatevol();
@ -221,9 +225,9 @@ VARP(minmillis, 0, 5, 1000);
// cheap hack to get rid of initial sparklies, even when triple // cheap hack to get rid of initial sparklies, even when triple
// buffering etc. // buffering etc.
if (_framesInMap++ < 5) { if (_framesInMap++ < 5) {
player1->yaw += 5; player1.yaw += 5;
gl_drawframe(_width, _height, fps); gl_drawframe(_width, _height, fps);
player1->yaw -= 5; player1.yaw -= 5;
} }
gl_drawframe(_width, _height, fps); gl_drawframe(_width, _height, fps);

55
src/DynamicEntity.h Normal file
View file

@ -0,0 +1,55 @@
#import <ObjFW/ObjFW.h>
// players & monsters
@interface DynamicEntity: OFObject <OFCopying>
@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

235
src/DynamicEntity.mm Normal file
View file

@ -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

View file

@ -1,11 +1,11 @@
#import <ObjFW/ObjFW.h> #import <ObjFW/ObjFW.h>
typedef struct dynent dynent; @class DynamicEntity;
@interface Projectile: OFObject @interface Projectile: OFObject
@property (nonatomic) OFVector3D o, to; @property (nonatomic) OFVector3D o, to;
@property (nonatomic) float speed; @property (nonatomic) float speed;
@property (nonatomic) dynent *owner; @property (nonatomic) DynamicEntity *owner;
@property (nonatomic) int gun; @property (nonatomic) int gun;
@property (nonatomic) bool inuse, local; @property (nonatomic) bool inuse, local;
@end @end

View file

@ -2,12 +2,16 @@
#include "cube.h" #include "cube.h"
ENetHost *clienthost = NULL; #import "DynamicEntity.h"
int connecting = 0;
int connattempts = 0; static ENetHost *clienthost = NULL;
int disconnecting = 0; static int connecting = 0;
int clientnum = -1; // our client id in the game static int connattempts = 0;
bool c2sinit = false; // whether we need to tell the other clients our stats 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 int
getclientnum() getclientnum()
@ -59,9 +63,13 @@ throttle()
void void
newname(OFString *name) newname(OFString *name)
{ {
c2sinit = false;
@autoreleasepool { @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) COMMANDN(name, newname, ARG_1STR)
@ -69,9 +77,13 @@ COMMANDN(name, newname, ARG_1STR)
void void
newteam(OFString *name) newteam(OFString *name)
{ {
c2sinit = false;
@autoreleasepool { @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) COMMANDN(team, newteam, ARG_1STR)
@ -79,8 +91,8 @@ COMMANDN(team, newteam, ARG_1STR)
void void
writeclientinfo(OFStream *stream) writeclientinfo(OFStream *stream)
{ {
[stream writeFormat:@"name \"%s\"\nteam \"%s\"\n", player1->name, [stream writeFormat:@"name \"%@\"\nteam \"%@\"\n", player1.name,
player1->team]; player1.team];
} }
void void
@ -137,8 +149,8 @@ disconnect(int onlyclean, int async)
disconnecting = 0; disconnecting = 0;
clientnum = -1; clientnum = -1;
c2sinit = false; c2sinit = false;
player1->lifesequence = 0; player1.lifesequence = 0;
loopv(players) zapdynent(players[i]); [players removeAllObjects];
localdisconnect(); localdisconnect();
@ -168,7 +180,7 @@ static OFString *ctext;
void void
toserver(OFString *text) toserver(OFString *text)
{ {
conoutf(@"%s:\f %@", player1->name, text); conoutf(@"%@:\f %@", player1.name, text);
ctext = text; ctext = text;
} }
@ -271,8 +283,9 @@ sendpackettoserv(void *packet)
localclienttoserver((ENetPacket *)packet); localclienttoserver((ENetPacket *)packet);
} }
// send update to the server
void void
c2sinfo(dynent *d) // send update to the server c2sinfo(DynamicEntity *d)
{ {
@autoreleasepool { @autoreleasepool {
if (clientnum < 0) if (clientnum < 0)
@ -295,26 +308,24 @@ c2sinfo(dynent *d) // send update to the server
} else { } else {
putint(p, SV_POS); putint(p, SV_POS);
putint(p, clientnum); putint(p, clientnum);
putint(p, // quantize coordinates to 1/16th of a cube, between 1
(int)(d->o.x * // and 3 bytes
DMF)); // quantize coordinates to 1/16th putint(p, (int)(d.o.x * DMF));
// of a cube, between 1 and 3 bytes putint(p, (int)(d.o.y * DMF));
putint(p, (int)(d->o.y * DMF)); putint(p, (int)(d.o.z * DMF));
putint(p, (int)(d->o.z * DMF)); putint(p, (int)(d.yaw * DAF));
putint(p, (int)(d->yaw * DAF)); putint(p, (int)(d.pitch * DAF));
putint(p, (int)(d->pitch * DAF)); putint(p, (int)(d.roll * DAF));
putint(p, (int)(d->roll * DAF)); // quantize to 1/100, almost always 1 byte
putint( putint(p, (int)(d.vel.x * DVF));
p, (int)(d->vel.x * DVF)); // quantize to 1/100, putint(p, (int)(d.vel.y * DVF));
// almost always 1 byte putint(p, (int)(d.vel.z * 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, // pack rest in 1 byte: strafe:2, move:2, onfloor:1,
// state:3 // state:3
putint(p, putint(p,
(d->strafe & 3) | ((d->move & 3) << 2) | (d.strafe & 3) | ((d.move & 3) << 2) |
(((int)d->onfloor) << 4) | (((int)d.onfloor) << 4) |
((editmode ? CS_EDITING : d->state) << 5)); ((editmode ? CS_EDITING : d.state) << 5));
if (senditemstoserver) { if (senditemstoserver) {
packet->flags = ENET_PACKET_FLAG_RELIABLE; packet->flags = ENET_PACKET_FLAG_RELIABLE;
@ -332,14 +343,14 @@ c2sinfo(dynent *d) // send update to the server
sendstring(ctext, p); sendstring(ctext, p);
ctext = @""; ctext = @"";
} }
if (!c2sinit) // tell other clients who I am // tell other clients who I am
{ if (!c2sinit) {
packet->flags = ENET_PACKET_FLAG_RELIABLE; packet->flags = ENET_PACKET_FLAG_RELIABLE;
c2sinit = true; c2sinit = true;
putint(p, SV_INITC2S); putint(p, SV_INITC2S);
sendstring(@(player1->name), p); sendstring(player1.name, p);
sendstring(@(player1->team), p); sendstring(player1.team, p);
putint(p, player1->lifesequence); putint(p, player1.lifesequence);
} }
for (OFData *msg in messages) { for (OFData *msg in messages) {
// send messages collected during the previous // send messages collected during the previous

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
// render players & monsters // render players & monsters
// very messy ad-hoc handling of animation frames, should be made more // very messy ad-hoc handling of animation frames, should be made more
// configurable // configurable
@ -14,13 +16,14 @@ int range[] = { 6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1,
1 }; 1 };
void 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; int n = 3;
float speed = 100.0f; 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); int basetime = -((intptr_t)d & 0xFFF);
if (d->state == CS_DEAD) { if (d.state == CS_DEAD) {
int r; int r;
if (hellpig) { if (hellpig) {
n = 2; n = 2;
@ -29,8 +32,8 @@ renderclient(dynent *d, bool team, OFString *mdlname, bool hellpig, float scale)
n = (intptr_t)d % 3; n = (intptr_t)d % 3;
r = range[n]; r = range[n];
} }
basetime = d->lastaction; basetime = d.lastaction;
int t = lastmillis - d->lastaction; int t = lastmillis - d.lastaction;
if (t < 0 || t > 20000) if (t < 0 || t > 20000)
return; return;
if (t > (r - 1) * 100) { if (t > (r - 1) * 100) {
@ -43,33 +46,33 @@ renderclient(dynent *d, bool team, OFString *mdlname, bool hellpig, float scale)
if (mz < -1000) if (mz < -1000)
return; return;
// mdl = (((int)d>>6)&1)+1; // 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; // scale = 1.2f;
} else if (d->state == CS_EDITING) { } else if (d.state == CS_EDITING) {
n = 16; n = 16;
} else if (d->state == CS_LAGGED) { } else if (d.state == CS_LAGGED) {
n = 17; n = 17;
} else if (d->monsterstate == M_ATTACKING) { } else if (d.monsterstate == M_ATTACKING) {
n = 8; n = 8;
} else if (d->monsterstate == M_PAIN) { } else if (d.monsterstate == M_PAIN) {
n = 10; n = 10;
} else if ((!d->move && !d->strafe) || !d->moving) { } else if ((!d.move && !d.strafe) || !d.moving) {
n = 12; n = 12;
} else if (!d->onfloor && d->timeinair > 100) { } else if (!d.onfloor && d.timeinair > 100) {
n = 18; n = 18;
} else { } else {
n = 14; n = 14;
speed = 1200 / d->maxspeed * scale; speed = 1200 / d.maxspeed * scale;
if (hellpig) if (hellpig)
speed = 300 / d->maxspeed; speed = 300 / d.maxspeed;
} }
if (hellpig) { if (hellpig) {
n++; n++;
scale *= 32; scale *= 32;
mz -= 1.9f; mz -= 1.9f;
} }
rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, 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); d.yaw + 90, d.pitch / 2, team, scale, speed, 0, basetime);
} }
extern int democlientnum; extern int democlientnum;
@ -77,10 +80,15 @@ extern int democlientnum;
void void
renderclients() renderclients()
{ {
dynent *d; size_t i = 0;
loopv(players) if ((d = players[i]) && for (id player in players) {
(!demoplayback || i != democlientnum)) renderclient(d, if (player != [OFNull null] &&
isteam(player1->team, d->team), @"monster/ogro", false, 1.0f); (!demoplayback || i != democlientnum))
renderclient(player,
isteam(player1.team, [player team]),
@"monster/ogro", false, 1.0f);
i++;
}
} }
// creation of scoreboard pseudo-menu // creation of scoreboard pseudo-menu
@ -97,15 +105,15 @@ showscores(bool on)
static OFMutableArray<OFString *> *scoreLines; static OFMutableArray<OFString *> *scoreLines;
void void
renderscore(dynent *d) renderscore(DynamicEntity *d)
{ {
@autoreleasepool { @autoreleasepool {
OFString *lag = [OFString stringWithFormat:@"%d", d->plag]; OFString *lag = [OFString stringWithFormat:@"%d", d.plag];
OFString *name = [OFString stringWithFormat:@"(%s)", d->name]; OFString *name = [OFString stringWithFormat:@"(%@)", d.name];
OFString *line = [OFString OFString *line =
stringWithFormat:@"%d\t%@\t%d\t%s\t%@", d->frags, [OFString stringWithFormat:@"%d\t%@\t%d\t%@\t%@", d.frags,
(d->state == CS_LAGGED ? @"LAG" : lag), d->ping, d->team, (d.state == CS_LAGGED ? @"LAG" : lag), d.ping,
(d->state == CS_DEAD ? name : @(d->name))]; d.team, (d.state == CS_DEAD ? name : d.name)];
if (scoreLines == nil) if (scoreLines == nil)
scoreLines = [[OFMutableArray alloc] init]; scoreLines = [[OFMutableArray alloc] init];
@ -118,21 +126,16 @@ renderscore(dynent *d)
static const int maxTeams = 4; static const int maxTeams = 4;
static OFString *teamName[maxTeams]; static OFString *teamName[maxTeams];
static int teamScore[maxTeams], teamsUsed; static int teamScore[maxTeams];
static size_t teamsUsed;
void void
addteamscore(dynent *d) addteamscore(DynamicEntity *d)
{ {
if (d == NULL)
return;
@autoreleasepool { @autoreleasepool {
OFString *team = @(d->team); for (size_t i = 0; i < teamsUsed; i++) {
if ([teamName[i] isEqual:d.team]) {
loopi(teamsUsed) teamScore[i] += d.frags;
{
if ([teamName[i] isEqual:team]) {
teamScore[i] += d->frags;
return; return;
} }
} }
@ -140,8 +143,8 @@ addteamscore(dynent *d)
if (teamsUsed == maxTeams) if (teamsUsed == maxTeams)
return; return;
teamName[teamsUsed] = @(d->team); teamName[teamsUsed] = d.team;
teamScore[teamsUsed++] = d->frags; teamScore[teamsUsed++] = d.frags;
} }
} }
@ -153,19 +156,21 @@ renderscores()
[scoreLines removeAllObjects]; [scoreLines removeAllObjects];
if (!demoplayback) if (!demoplayback)
renderscore(player1); renderscore(player1);
loopv(players) if (players[i]) renderscore(players[i]); for (id player in players)
if (player != [OFNull null])
renderscore(player);
sortmenu(); sortmenu();
if (m_teammode) { if (m_teammode) {
teamsUsed = 0; teamsUsed = 0;
loopv(players) addteamscore(players[i]); for (id player in players)
if (player != [OFNull null])
addteamscore(player);
if (!demoplayback) if (!demoplayback)
addteamscore(player1); addteamscore(player1);
OFMutableString *teamScores = [[OFMutableString alloc] init]; OFMutableString *teamScores = [[OFMutableString alloc] init];
loopj(teamsUsed) for (size_t j = 0; j < teamsUsed; j++)
{
[teamScores appendFormat:@"[ %@: %d ]", teamName[j], [teamScores appendFormat:@"[ %@: %d ]", teamName[j],
teamScore[j]]; teamScore[j]];
}
menumanual(0, scoreLines.count, @""); menumanual(0, scoreLines.count, @"");
@autoreleasepool { @autoreleasepool {
menumanual(0, scoreLines.count + 1, teamScores); menumanual(0, scoreLines.count + 1, teamScores);

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
int nextmode = 0; // nextmode becomes gamemode after next map load int nextmode = 0; // nextmode becomes gamemode after next map load
VAR(gamemode, 1, 0, 0); VAR(gamemode, 1, 0, 0);
@ -14,8 +16,15 @@ COMMAND(mode, ARG_1INT)
bool intermission = false; bool intermission = false;
dynent *player1 = newdynent(); // our client DynamicEntity *player1; // our client
dvector players; // other clients OFMutableArray *players; // other clients
void
initPlayers()
{
player1 = newdynent();
players = [[OFMutableArray alloc] init];
}
VARP(sensitivity, 0, 10, 10000); VARP(sensitivity, 0, 10, 10000);
VARP(sensitivityscale, 1, 1, 10000); VARP(sensitivityscale, 1, 1, 10000);
@ -32,49 +41,51 @@ getclientmap()
} }
void void
resetmovement(dynent *d) resetmovement(DynamicEntity *d)
{ {
d->k_left = false; d.k_left = false;
d->k_right = false; d.k_right = false;
d->k_up = false; d.k_up = false;
d->k_down = false; d.k_down = false;
d->jumpnext = false; d.jumpnext = false;
d->strafe = 0; d.strafe = 0;
d->move = 0; d.move = 0;
} }
// reset player state not persistent accross spawns
void void
spawnstate(dynent *d) // reset player state not persistent accross spawns spawnstate(DynamicEntity *d)
{ {
resetmovement(d); resetmovement(d);
d->vel.x = d->vel.y = d->vel.z = 0; d.vel = OFMakeVector3D(0, 0, 0);
d->onfloor = false; d.onfloor = false;
d->timeinair = 0; d.timeinair = 0;
d->health = 100; d.health = 100;
d->armour = 50; d.armour = 50;
d->armourtype = A_BLUE; d.armourtype = A_BLUE;
d->quadmillis = 0; d.quadmillis = 0;
d->lastattackgun = d->gunselect = GUN_SG; d.lastattackgun = d.gunselect = GUN_SG;
d->gunwait = 0; d.gunwait = 0;
d->attacking = false; d.attacking = false;
d->lastaction = 0; d.lastaction = 0;
loopi(NUMGUNS) d->ammo[i] = 0; loopi(NUMGUNS) d.ammo[i] = 0;
d->ammo[GUN_FIST] = 1; d.ammo[GUN_FIST] = 1;
if (m_noitems) { if (m_noitems) {
d->gunselect = GUN_RIFLE; d.gunselect = GUN_RIFLE;
d->armour = 0; d.armour = 0;
if (m_noitemsrail) { if (m_noitemsrail) {
d->health = 1; d.health = 1;
d->ammo[GUN_RIFLE] = 100; d.ammo[GUN_RIFLE] = 100;
} else { } else {
if (gamemode == 12) { if (gamemode == 12) {
d->gunselect = GUN_FIST; // eihrul's secret "instafist" mode
d.gunselect = GUN_FIST;
return; return;
} // eihrul's secret "instafist" mode }
d->health = 256; d.health = 256;
if (m_tarena) { if (m_tarena) {
int gun1 = rnd(4) + 1; int gun1 = rnd(4) + 1;
baseammo(d->gunselect = gun1); baseammo(d.gunselect = gun1);
for (;;) { for (;;) {
int gun2 = rnd(4) + 1; int gun2 = rnd(4) + 1;
if (gun1 != gun2) { if (gun1 != gun2) {
@ -82,47 +93,44 @@ spawnstate(dynent *d) // reset player state not persistent accross spawns
break; break;
} }
} }
} else if (m_arena) // insta arena } else if (m_arena) {
{ // insta arena
d->ammo[GUN_RIFLE] = 100; d.ammo[GUN_RIFLE] = 100;
} else // efficiency
{
loopi(4) baseammo(i + 1);
d->gunselect = GUN_CG;
}
d->ammo[GUN_CG] /= 2;
}
} else { } else {
d->ammo[GUN_SG] = 5; // efficiency
loopi(4) baseammo(i + 1);
d.gunselect = GUN_CG;
} }
d.ammo[GUN_CG] /= 2;
}
} else
d.ammo[GUN_SG] = 5;
} }
dynent * DynamicEntity *
newdynent() // create a new blank player or monster newdynent() // create a new blank player or monster
{ {
dynent *d = (dynent *)OFAllocMemory(1, sizeof(dynent)); DynamicEntity *d = [[DynamicEntity alloc] init];
d->o.x = 0; d.o = OFMakeVector3D(0, 0, 0);
d->o.y = 0; d.yaw = 270;
d->o.z = 0; d.pitch = 0;
d->yaw = 270; d.roll = 0;
d->pitch = 0; d.maxspeed = 22;
d->roll = 0; d.outsidemap = false;
d->maxspeed = 22; d.inwater = false;
d->outsidemap = false; d.radius = 1.1f;
d->inwater = false; d.eyeheight = 3.2f;
d->radius = 1.1f; d.aboveeye = 0.7f;
d->eyeheight = 3.2f; d.frags = 0;
d->aboveeye = 0.7f; d.plag = 0;
d->frags = 0; d.ping = 0;
d->plag = 0; d.lastupdate = lastmillis;
d->ping = 0; d.enemy = NULL;
d->lastupdate = lastmillis; d.monsterstate = 0;
d->enemy = NULL; d.name = d.team = @"";
d->monsterstate = 0; d.blocked = false;
d->name[0] = d->team[0] = 0; d.lifesequence = 0;
d->blocked = false; d.state = CS_ALIVE;
d->lifesequence = 0;
d->state = CS_ALIVE;
spawnstate(d); spawnstate(d);
return d; return d;
} }
@ -135,17 +143,17 @@ respawnself()
} }
void 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++; alive++;
if (lastteam && strcmp(lastteam, d->team)) if (![*lastteam isEqual:d.team])
oneteam = false; oneteam = false;
lastteam = d->team; *lastteam = d.team;
} else { } else
dead++; dead++;
} }
}
int arenarespawnwait = 0; int arenarespawnwait = 0;
int arenadetectwait = 0; int arenadetectwait = 0;
@ -162,11 +170,13 @@ arenarespawn()
} else if (arenadetectwait == 0 || arenadetectwait < lastmillis) { } else if (arenadetectwait == 0 || arenadetectwait < lastmillis) {
arenadetectwait = 0; arenadetectwait = 0;
int alive = 0, dead = 0; int alive = 0, dead = 0;
char *lastteam = NULL; OFString *lastteam = nil;
bool oneteam = true; bool oneteam = true;
loopv(players) if (players[i]) for (id player in players)
arenacount(players[i], alive, dead, lastteam, oneteam); if (player != [OFNull null])
arenacount(player1, alive, dead, lastteam, oneteam); arenacount(
player, alive, dead, &lastteam, oneteam);
arenacount(player1, alive, dead, &lastteam, oneteam);
if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) { if (dead > 0 && (alive <= 1 || (m_teammode && oneteam))) {
conoutf( conoutf(
@"arena round is over! next round in 5 seconds..."); @"arena round is over! next round in 5 seconds...");
@ -177,43 +187,39 @@ arenarespawn()
conoutf(@"everyone died!"); conoutf(@"everyone died!");
arenarespawnwait = lastmillis + 5000; arenarespawnwait = lastmillis + 5000;
arenadetectwait = lastmillis + 10000; arenadetectwait = lastmillis + 10000;
player1->roll = 0; player1.roll = 0;
} }
} }
} }
void
zapdynent(dynent *&d)
{
OFFreeMemory(d);
d = NULL;
}
extern int democlientnum; extern int democlientnum;
void void
otherplayers() otherplayers()
{ {
loopv(players) if (players[i]) size_t i = 0;
{ for (id player in players) {
const int lagtime = lastmillis - players[i]->lastupdate; if (player != [OFNull null]) {
if (lagtime > 1000 && players[i]->state == CS_ALIVE) { const int lagtime = lastmillis - [player lastupdate];
players[i]->state = CS_LAGGED; if (lagtime > 1000 && [player state] == CS_ALIVE) {
[player setState:CS_LAGGED];
i++;
continue; continue;
} }
if (lagtime && players[i]->state != CS_DEAD && if (lagtime && [player state] != CS_DEAD &&
(!demoplayback || i != democlientnum)) (!demoplayback || i != democlientnum))
moveplayer( // use physics to extrapolate player position
players[i], 2, false); // use physics to extrapolate moveplayer(player, 2, false);
// player position }
i++;
} }
} }
void void
respawn() respawn()
{ {
if (player1->state == CS_DEAD) { if (player1.state == CS_DEAD) {
player1->attacking = false; player1.attacking = false;
if (m_arena) { if (m_arena) {
conoutf(@"waiting for new round to start..."); conoutf(@"waiting for new round to start...");
return; return;
@ -262,19 +268,19 @@ updateworld(int millis) // main game update loop
otherplayers(); otherplayers();
if (!demoplayback) { if (!demoplayback) {
monsterthink(); monsterthink();
if (player1->state == CS_DEAD) { if (player1.state == CS_DEAD) {
if (lastmillis - player1->lastaction < 2000) { if (lastmillis - player1.lastaction < 2000) {
player1->move = player1->strafe = 0; player1.move = player1.strafe = 0;
moveplayer(player1, 10, false); moveplayer(player1, 10, false);
} else if (!m_arena && !m_sp && } else if (!m_arena && !m_sp &&
lastmillis - player1->lastaction > 10000) lastmillis - player1.lastaction > 10000)
respawn(); respawn();
} else if (!intermission) { } else if (!intermission) {
moveplayer(player1, 20, true); moveplayer(player1, 20, true);
checkitems(); checkitems();
} }
c2sinfo(player1); // do this last, to reduce the // do this last, to reduce the effective frame lag
// effective frame lag c2sinfo(player1);
} }
} }
lastmillis = millis; lastmillis = millis;
@ -282,46 +288,43 @@ updateworld(int millis) // main game update loop
// brute force but effective way to find a free spawn spot in the map // brute force but effective way to find a free spawn spot in the map
void void
entinmap(dynent *d) entinmap(DynamicEntity *d)
{ {
loopi(100) // try max 100 times loopi(100) // try max 100 times
{ {
float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance float dx = (rnd(21) - 10) / 10.0f * i; // increasing distance
float dy = (rnd(21) - 10) / 10.0f * i; float dy = (rnd(21) - 10) / 10.0f * i;
d->o.x += dx; OFVector3D old = d.o;
d->o.y += dy; d.o = OFMakeVector3D(d.o.x + dx, d.o.y + dy, d.o.z);
if (collide(d, true, 0, 0)) if (collide(d, true, 0, 0))
return; return;
d->o.x -= dx; d.o = old;
d->o.y -= dy;
} }
conoutf(@"can't find entity spawn spot! (%d, %d)", (int)d->o.x, conoutf(
(int)d->o.y); @"can't find entity spawn spot! (%d, %d)", (int)d.o.x, (int)d.o.y);
// leave ent at original pos, possibly stuck // leave ent at original pos, possibly stuck
} }
int spawncycle = -1; int spawncycle = -1;
int fixspawn = 2; int fixspawn = 2;
// place at random spawn. also used by monsters!
void void
spawnplayer(dynent *d) // place at random spawn. also used by monsters! spawnplayer(DynamicEntity *d)
{ {
int r = fixspawn-- > 0 ? 4 : rnd(10) + 1; int r = fixspawn-- > 0 ? 4 : rnd(10) + 1;
loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1); loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle + 1);
if (spawncycle != -1) { if (spawncycle != -1) {
d->o.x = ents[spawncycle].x; d.o = OFMakeVector3D(
d->o.y = ents[spawncycle].y; ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z);
d->o.z = ents[spawncycle].z; d.yaw = ents[spawncycle].attr1;
d->yaw = ents[spawncycle].attr1; d.pitch = 0;
d->pitch = 0; d.roll = 0;
d->roll = 0; } else
} else { d.o = OFMakeVector3D((float)ssize / 2, (float)ssize / 2, 4);
d->o.x = d->o.y = (float)ssize / 2;
d->o.z = 4;
}
entinmap(d); entinmap(d);
spawnstate(d); spawnstate(d);
d->state = CS_ALIVE; d.state = CS_ALIVE;
} }
// movement input code // movement input code
@ -329,9 +332,9 @@ spawnplayer(dynent *d) // place at random spawn. also used by monsters!
#define dir(name, v, d, s, os) \ #define dir(name, v, d, s, os) \
void name(bool isdown) \ void name(bool isdown) \
{ \ { \
player1->s = isdown; \ player1.s = isdown; \
player1->v = isdown ? d : (player1->os ? -(d) : 0); \ player1.v = isdown ? d : (player1.os ? -(d) : 0); \
player1->lastmove = lastmillis; \ player1.lastmove = lastmillis; \
} }
dir(backward, move, -1, k_down, k_up); dir(backward, move, -1, k_down, k_up);
@ -346,14 +349,14 @@ attack(bool on)
return; return;
if (editmode) if (editmode)
editdrag(on); editdrag(on);
else if (player1->attacking = on) else if ((player1.attacking = on))
respawn(); respawn();
} }
void void
jumpn(bool on) jumpn(bool on)
{ {
if (!intermission && (player1->jumpnext = on)) if (!intermission && (player1.jumpnext = on))
respawn(); respawn();
} }
@ -369,24 +372,24 @@ void
fixplayer1range() fixplayer1range()
{ {
const float MAXPITCH = 90.0f; const float MAXPITCH = 90.0f;
if (player1->pitch > MAXPITCH) if (player1.pitch > MAXPITCH)
player1->pitch = MAXPITCH; player1.pitch = MAXPITCH;
if (player1->pitch < -MAXPITCH) if (player1.pitch < -MAXPITCH)
player1->pitch = -MAXPITCH; player1.pitch = -MAXPITCH;
while (player1->yaw < 0.0f) while (player1.yaw < 0.0f)
player1->yaw += 360.0f; player1.yaw += 360.0f;
while (player1->yaw >= 360.0f) while (player1.yaw >= 360.0f)
player1->yaw -= 360.0f; player1.yaw -= 360.0f;
} }
void void
mousemove(int dx, int dy) mousemove(int dx, int dy)
{ {
if (player1->state == CS_DEAD || intermission) if (player1.state == CS_DEAD || intermission)
return; return;
const float SENSF = 33.0f; // try match quake sens const float SENSF = 33.0f; // try match quake sens
player1->yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale); player1.yaw += (dx / SENSF) * (sensitivity / (float)sensitivityscale);
player1->pitch -= (dy / SENSF) * player1.pitch -= (dy / SENSF) *
(sensitivity / (float)sensitivityscale) * (invmouse ? -1 : 1); (sensitivity / (float)sensitivityscale) * (invmouse ? -1 : 1);
fixplayer1range(); fixplayer1range();
} }
@ -394,67 +397,65 @@ mousemove(int dx, int dy)
// damage arriving from the network, monsters, yourself, all ends up here. // damage arriving from the network, monsters, yourself, all ends up here.
void 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; return;
damageblend(damage); damageblend(damage);
demoblend(damage); demoblend(damage);
int ad = damage * (player1->armourtype + 1) * 20 / // let armour absorb when possible
100; // let armour absorb when possible int ad = damage * (player1.armourtype + 1) * 20 / 100;
if (ad > player1->armour) if (ad > player1.armour)
ad = player1->armour; ad = player1.armour;
player1->armour -= ad; player1.armour -= ad;
damage -= ad; damage -= ad;
float droll = damage / 0.5f; float droll = damage / 0.5f;
player1->roll += player1->roll > 0 player1.roll += player1.roll > 0
? droll ? droll
: (player1->roll < 0 : (player1.roll < 0
? -droll ? -droll
: (rnd(2) ? droll : (rnd(2) ? droll
: -droll)); // give player a kick depending : -droll)); // give player a kick depending
// on amount of damage // on amount of damage
if ((player1->health -= damage) <= 0) { if ((player1.health -= damage) <= 0) {
if (actor == -2) { if (actor == -2) {
conoutf(@"you got killed by %s!", act->name); conoutf(@"you got killed by %@!", act.name);
} else if (actor == -1) { } else if (actor == -1) {
actor = getclientnum(); actor = getclientnum();
conoutf(@"you suicided!"); conoutf(@"you suicided!");
addmsg(1, 2, SV_FRAGS, --player1->frags); addmsg(1, 2, SV_FRAGS, --player1.frags);
} else { } else {
dynent *a = getclient(actor); DynamicEntity *a = getclient(actor);
if (a) { if (a != nil) {
if (isteam(a->team, player1->team)) { if (isteam(a.team, player1.team))
conoutf(@"you got fragged by a " conoutf(@"you got fragged by a "
@"teammate (%s)", @"teammate (%@)",
a->name); a.name);
} else { else
conoutf( conoutf(
@"you got fragged by %s", a->name); @"you got fragged by %@", a.name);
}
} }
} }
showscores(true); showscores(true);
addmsg(1, 2, SV_DIED, actor); addmsg(1, 2, SV_DIED, actor);
player1->lifesequence++; player1.lifesequence++;
player1->attacking = false; player1.attacking = false;
player1->state = CS_DEAD; player1.state = CS_DEAD;
player1->pitch = 0; player1.pitch = 0;
player1->roll = 60; player1.roll = 60;
playsound(S_DIE1 + rnd(2)); playsound(S_DIE1 + rnd(2));
spawnstate(player1); spawnstate(player1);
player1->lastaction = lastmillis; player1.lastaction = lastmillis;
} else { } else
playsound(S_PAIN6); playsound(S_PAIN6);
} }
}
void void
timeupdate(int timeremain) timeupdate(int timeremain)
{ {
if (!timeremain) { if (!timeremain) {
intermission = true; intermission = true;
player1->attacking = false; player1.attacking = false;
conoutf(@"intermission:"); conoutf(@"intermission:");
conoutf(@"game has ended!"); conoutf(@"game has ended!");
showscores(true); showscores(true);
@ -463,16 +464,31 @@ timeupdate(int timeremain)
} }
} }
dynent * DynamicEntity *
getclient(int cn) // ensure valid entity getclient(int cn) // ensure valid entity
{ {
if (cn < 0 || cn >= MAXCLIENTS) { if (cn < 0 || cn >= MAXCLIENTS) {
neterr(@"clientnum"); neterr(@"clientnum");
return NULL; return nil;
} }
while (cn >= players.length()) if (players == nil)
players.add(NULL); players = [[OFMutableArray alloc] init];
return players[cn] ? players[cn] : (players[cn] = newdynent()); 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 void
@ -494,8 +510,10 @@ startmap(OFString *name) // called just after a map load
projreset(); projreset();
spawncycle = -1; spawncycle = -1;
spawnplayer(player1); spawnplayer(player1);
player1->frags = 0; player1.frags = 0;
loopv(players) if (players[i]) players[i]->frags = 0; for (id player in players)
if (player != [OFNull null])
[player setFrags:0];
resetspawns(); resetspawns();
clientmap = name; clientmap = name;
if (editmode) if (editmode)

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
extern int clientnum; extern int clientnum;
extern bool c2sinit, senditemstoserver; extern bool c2sinit, senditemstoserver;
extern OFString *toservermap; extern OFString *toservermap;
@ -32,31 +34,35 @@ changemap(OFString *name) // request map change, server may ignore
// just don't overlap with our client // just don't overlap with our client
void void
updatepos(dynent *d) updatepos(DynamicEntity *d)
{ {
const float r = player1->radius + d->radius; const float r = player1.radius + d.radius;
const float dx = player1->o.x - d->o.x; const float dx = player1.o.x - d.o.x;
const float dy = player1->o.y - d->o.y; const float dy = player1.o.y - d.o.y;
const float dz = player1->o.z - d->o.z; const float dz = player1.o.z - d.o.z;
const float rz = player1->aboveeye + d->eyeheight; const float rz = player1.aboveeye + d.eyeheight;
const float fx = (float)fabs(dx), fy = (float)fabs(dy), const float fx = (float)fabs(dx), fy = (float)fabs(dy),
fz = (float)fabs(dz); fz = (float)fabs(dz);
if (fx < r && fy < r && fz < rz && d->state != CS_DEAD) { if (fx < r && fy < r && fz < rz && d.state != CS_DEAD) {
if (fx < fy) if (fx < fy)
d->o.y += dy < 0 ? r - fy : -(r - fy); // push aside // push aside
d.o = OFMakeVector3D(d.o.x,
d.o.y + (dy < 0 ? r - fy : -(r - fy)), d.o.z);
else else
d->o.x += dx < 0 ? r - fx : -(r - fx); d.o = OFMakeVector3D(
d.o.x + (dx < 0 ? r - fx : -(r - fx)), d.o.y,
d.o.z);
} }
int lagtime = lastmillis - d->lastupdate; int lagtime = lastmillis - d.lastupdate;
if (lagtime) { if (lagtime) {
d->plag = (d->plag * 5 + lagtime) / 6; d.plag = (d.plag * 5 + lagtime) / 6;
d->lastupdate = lastmillis; d.lastupdate = lastmillis;
} }
} }
// processes any updates from the server
void void
localservertoclient( localservertoclient(uchar *buf, int len)
uchar *buf, int len) // processes any updates from the server
{ {
if (ENET_NET_TO_HOST_16(*(ushort *)buf) != len) if (ENET_NET_TO_HOST_16(*(ushort *)buf) != len)
neterr(@"packet length"); neterr(@"packet length");
@ -66,7 +72,7 @@ localservertoclient(
uchar *p = buf + 2; uchar *p = buf + 2;
char text[MAXTRANS]; char text[MAXTRANS];
int cn = -1, type; int cn = -1, type;
dynent *d = NULL; DynamicEntity *d = nil;
bool mapchanged = false; bool mapchanged = false;
while (p < end) while (p < end)
@ -104,42 +110,42 @@ localservertoclient(
break; break;
} }
case SV_POS: // position of another client case SV_POS: {
{ // position of another client
cn = getint(p); cn = getint(p);
d = getclient(cn); d = getclient(cn);
if (!d) if (d == nil)
return; return;
d->o.x = getint(p) / DMF; d.o = OFMakeVector3D(
d->o.y = getint(p) / DMF; getint(p) / DMF, getint(p) / DMF, getint(p) / DMF);
d->o.z = getint(p) / DMF; d.yaw = getint(p) / DAF;
d->yaw = getint(p) / DAF; d.pitch = getint(p) / DAF;
d->pitch = getint(p) / DAF; d.roll = getint(p) / DAF;
d->roll = getint(p) / DAF; d.vel = OFMakeVector3D(
d->vel.x = getint(p) / DVF; getint(p) / DVF, getint(p) / DVF, getint(p) / DVF);
d->vel.y = getint(p) / DVF;
d->vel.z = getint(p) / DVF;
int f = getint(p); int f = getint(p);
d->strafe = (f & 3) == 3 ? -1 : f & 3; d.strafe = (f & 3) == 3 ? -1 : f & 3;
f >>= 2; f >>= 2;
d->move = (f & 3) == 3 ? -1 : f & 3; d.move = (f & 3) == 3 ? -1 : f & 3;
d->onfloor = (f >> 2) & 1; d.onfloor = (f >> 2) & 1;
int state = f >> 3; int state = f >> 3;
if (state == CS_DEAD && d->state != CS_DEAD) if (state == CS_DEAD && d.state != CS_DEAD)
d->lastaction = lastmillis; d.lastaction = lastmillis;
d->state = state; d.state = state;
if (!demoplayback) if (!demoplayback)
updatepos(d); updatepos(d);
break; break;
} }
case SV_SOUND: case SV_SOUND: {
playsound(getint(p), &d->o); OFVector3D loc = d.o;
playsound(getint(p), &loc);
break; break;
}
case SV_TEXT: case SV_TEXT:
sgetstr(); sgetstr();
conoutf(@"%s:\f %s", d->name, text); conoutf(@"%@:\f %s", d.name, text);
break; break;
case SV_MAPCHANGE: case SV_MAPCHANGE:
@ -173,35 +179,35 @@ localservertoclient(
break; break;
} }
case SV_INITC2S: // another client either connected or changed // another client either connected or changed name/team
// name/team case SV_INITC2S: {
{
sgetstr(); sgetstr();
if (d->name[0]) { if (d.name.length > 0) {
// already connected // already connected
if (strcmp(d->name, text)) if (![d.name isEqual:@(text)])
conoutf(@"%s is now known as %s", conoutf(@"%@ is now known as %s",
d->name, text); d.name, text);
} else { } else {
// new client // new client
c2sinit =
false; // send new players my info again // send new players my info again
c2sinit = false;
conoutf(@"connected: %s", text); conoutf(@"connected: %s", text);
} }
strcpy_s(d->name, text); d.name = @(text);
sgetstr(); sgetstr();
strcpy_s(d->team, text); d.team = @(text);
d->lifesequence = getint(p); d.lifesequence = getint(p);
break; break;
} }
case SV_CDIS: case SV_CDIS:
cn = getint(p); cn = getint(p);
if (!(d = getclient(cn))) if ((d = getclient(cn)) == nil)
break; break;
conoutf(@"player %s disconnected", conoutf(@"player %@ disconnected",
d->name[0] ? d->name : "[incompatible client]"); d.name.length ? d.name : @"[incompatible client]");
zapdynent(players[cn]); players[cn] = [OFNull null];
break; break;
case SV_SHOT: { case SV_SHOT: {
@ -224,49 +230,51 @@ localservertoclient(
int damage = getint(p); int damage = getint(p);
int ls = getint(p); int ls = getint(p);
if (target == clientnum) { if (target == clientnum) {
if (ls == player1->lifesequence) if (ls == player1.lifesequence)
selfdamage(damage, cn, d); selfdamage(damage, cn, d);
} else } else {
playsound( OFVector3D loc = getclient(target).o;
S_PAIN1 + rnd(5), &getclient(target)->o); playsound(S_PAIN1 + rnd(5), &loc);
}
break; break;
} }
case SV_DIED: { case SV_DIED: {
int actor = getint(p); int actor = getint(p);
if (actor == cn) { if (actor == cn) {
conoutf(@"%s suicided", d->name); conoutf(@"%@ suicided", d.name);
} else if (actor == clientnum) { } else if (actor == clientnum) {
int frags; int frags;
if (isteam(player1->team, d->team)) { if (isteam(player1.team, d.team)) {
frags = -1; frags = -1;
conoutf(@"you fragged a teammate (%s)", conoutf(@"you fragged a teammate (%@)",
d->name); d.name);
} else { } else {
frags = 1; frags = 1;
conoutf(@"you fragged %s", d->name); conoutf(@"you fragged %@", d.name);
} }
addmsg(1, 2, SV_FRAGS, player1->frags += frags); addmsg(
1, 2, SV_FRAGS, (player1.frags += frags));
} else { } else {
dynent *a = getclient(actor); DynamicEntity *a = getclient(actor);
if (a) { if (a != nil) {
if (isteam(a->team, d->name)) { if (isteam(a.team, d.name))
conoutf(@"%s fragged his " conoutf(@"%@ fragged his "
@"teammate (%s)", @"teammate (%@)",
a->name, d->name); a.name, d.name);
} else { else
conoutf(@"%s fragged %s", conoutf(@"%@ fragged %@",
a->name, d->name); a.name, d.name);
} }
} }
} OFVector3D loc = d.o;
playsound(S_DIE1 + rnd(2), &d->o); playsound(S_DIE1 + rnd(2), &loc);
d->lifesequence++; d.lifesequence++;
break; break;
} }
case SV_FRAGS: case SV_FRAGS:
players[cn]->frags = getint(p); [players[cn] setFrags:getint(p)];
break; break;
case SV_ITEMPICKUP: case SV_ITEMPICKUP:
@ -348,13 +356,13 @@ localservertoclient(
case SV_PONG: case SV_PONG:
addmsg(0, 2, SV_CLIENTPING, addmsg(0, 2, SV_CLIENTPING,
player1->ping = player1.ping =
(player1->ping * 5 + lastmillis - getint(p)) / (player1.ping * 5 + lastmillis - getint(p)) /
6); 6);
break; break;
case SV_CLIENTPING: case SV_CLIENTPING:
players[cn]->ping = getint(p); [players[cn] setPing:getint(p)];
break; break;
case SV_GAMEMODE: case SV_GAMEMODE:

View file

@ -8,6 +8,8 @@
#include "tools.h" #include "tools.h"
@class DynamicEntity;
@interface Cube: OFObject <OFApplicationDelegate> @interface Cube: OFObject <OFApplicationDelegate>
@property (class, readonly, nonatomic) Cube *sharedInstance; @property (class, readonly, nonatomic) Cube *sharedInstance;
@property (readonly, nonatomic) SDL_Window *window; @property (readonly, nonatomic) SDL_Window *window;
@ -128,41 +130,8 @@ enum {
NUMGUNS NUMGUNS
}; };
struct dynent // players & monsters // bump if dynent/netprotocol changes or any other savegame/demo data
{ #define SAVEGAMEVERSION 4
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
enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off
enum { enum {
@ -281,8 +250,6 @@ struct vertex {
uchar r, g, b, a; uchar r, g, b, a;
}; };
typedef vector<dynent *> dvector;
// globals ooh naughty // globals ooh naughty
extern sqr *world, extern sqr *world,
@ -290,9 +257,10 @@ extern sqr *world,
extern header hdr; // current map header extern header hdr; // current map header
extern int sfactor, ssize; // ssize = 2^sfactor extern int sfactor, ssize; // ssize = 2^sfactor
extern int cubicsize, mipsize; // cubicsize = ssize^2 extern int cubicsize, mipsize; // cubicsize = ssize^2
extern dynent // special client ent that receives input and acts as camera
*player1; // special client ent that receives input and acts as camera extern DynamicEntity *player1;
extern dvector players; // all the other clients (in multiplayer) // all the other clients (in multiplayer)
extern OFMutableArray *players;
extern bool editmode; extern bool editmode;
extern vector<entity> ents; // map entities extern vector<entity> ents; // map entities
extern OFVector3D worldpos; // current target of the crosshair in the world extern OFVector3D worldpos; // current target of the crosshair in the world
@ -318,28 +286,30 @@ extern bool demoplayback;
#define dotprod(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) #define dotprod(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
#define vmul(u, f) \ #define vmul(u, f) \
{ \ { \
(u).x *= (f); \ OFVector3D tmp_ = u; \
(u).y *= (f); \ float tmp2_ = f; \
(u).z *= (f); \ u = OFMakeVector3D( \
tmp_.x * tmp2_, tmp_.y * tmp2_, tmp_.z * tmp2_); \
} }
#define vdiv(u, f) \ #define vdiv(u, f) \
{ \ { \
(u).x /= (f); \ OFVector3D tmp_ = u; \
(u).y /= (f); \ float tmp2_ = f; \
(u).z /= (f); \ u = OFMakeVector3D( \
tmp_.x / tmp2_, tmp_.y / tmp2_, tmp_.z / tmp2_); \
} }
#define vadd(u, v) \ #define vadd(u, v) \
{ \ { \
(u).x += (v).x; \ OFVector3D tmp_ = u; \
(u).y += (v).y; \ u = OFMakeVector3D( \
(u).z += (v).z; \ tmp_.x + (v).x, tmp_.y + (v).y, tmp_.z + (v).z); \
}; }
#define vsub(u, v) \ #define vsub(u, v) \
{ \ { \
(u).x -= (v).x; \ OFVector3D tmp_ = u; \
(u).y -= (v).y; \ u = OFMakeVector3D( \
(u).z -= (v).z; \ tmp_.x - (v).x, tmp_.y - (v).y, tmp_.z - (v).z); \
}; }
#define vdist(d, v, e, s) \ #define vdist(d, v, e, s) \
OFVector3D v = s; \ OFVector3D v = s; \
vsub(v, e); \ vsub(v, e); \
@ -370,7 +340,7 @@ extern bool demoplayback;
#define m_sp (gamemode < 0) #define m_sp (gamemode < 0)
#define m_dmsp (gamemode == -1) #define m_dmsp (gamemode == -1)
#define m_classicsp (gamemode == -2) #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 enum // function signatures for script functions, see command.cpp
{ {

View file

@ -3,6 +3,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
bool editmode = false; bool editmode = false;
// the current selection, used by almost all editing commands // the current selection, used by almost all editing commands
@ -52,7 +54,7 @@ VAR(editing, 0, 0, 1);
void void
toggleedit() toggleedit()
{ {
if (player1->state == CS_DEAD) if (player1.state == CS_DEAD)
return; // do not allow dead players to edit to avoid state return; // do not allow dead players to edit to avoid state
// confusion // confusion
if (!editmode && !allowedittoggle()) if (!editmode && !allowedittoggle())
@ -63,7 +65,7 @@ toggleedit()
} else { } else {
resettagareas(); // clear trigger areas to allow them to be resettagareas(); // clear trigger areas to allow them to be
// edited // edited
player1->health = 100; player1.health = 100;
if (m_classicsp) if (m_classicsp)
monsterclear(); // all monsters back at their spawns for monsterclear(); // all monsters back at their spawns for
// editing // editing
@ -150,7 +152,7 @@ sheight(
void void
cursorupdate() // called every frame from hud cursorupdate() // called every frame from hud
{ {
flrceil = ((int)(player1->pitch >= 0)) * 2; flrceil = ((int)(player1.pitch >= 0)) * 2;
volatile float x = volatile float x =
worldpos.x; // volatile needed to prevent msvc7 optimizer bug? worldpos.x; // volatile needed to prevent msvc7 optimizer bug?
@ -166,8 +168,8 @@ cursorupdate() // called every frame from hud
// selected wall // selected wall
if (fabs(sheight(s, s, z) - z) > 1) { if (fabs(sheight(s, s, z) - z) > 1) {
x += x > player1->o.x ? 0.5f : -0.5f; // find right wall cube x += x > player1.o.x ? 0.5f : -0.5f; // find right wall cube
y += y > player1->o.y ? 0.5f : -0.5f; y += y > player1.o.y ? 0.5f : -0.5f;
cx = (int)x; cx = (int)x;
cy = (int)y; cy = (int)y;
@ -322,9 +324,9 @@ tofronttex() // maintain most recently used of the texture lists when applying
} }
void void
editdrag(bool isdown) editdrag(bool isDown)
{ {
if (dragging = isdown) { if ((dragging = isDown)) {
lastx = cx; lastx = cx;
lasty = cy; lasty = cy;
lasth = ch; lasth = ch;
@ -602,7 +604,7 @@ newent(OFString *what, OFString *a1, OFString *a2, OFString *a3, OFString *a4)
{ {
EDITSEL; EDITSEL;
@autoreleasepool { @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)[a1 longLongValueWithBase:0],
(int)[a2 longLongValueWithBase:0], (int)[a2 longLongValueWithBase:0],
(int)[a3 longLongValueWithBase:0], (int)[a3 longLongValueWithBase:0],

View file

@ -2,6 +2,7 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#import "MapModelInfo.h" #import "MapModelInfo.h"
vector<entity> ents; vector<entity> ents;
@ -122,14 +123,14 @@ struct itemstat {
void void
baseammo(int gun) 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 // these two functions are called when the server acknowledges that you really
// picked up the item (in multiplayer someone may grab it before you). // picked up the item (in multiplayer someone may grab it before you).
void static int
radditem(int i, int &v) radditem(int i, int v)
{ {
itemstat &is = itemstats[ents[i].type - I_SHELLS]; itemstat &is = itemstats[ents[i].type - I_SHELLS];
ents[i].spawned = false; ents[i].spawned = false;
@ -137,43 +138,44 @@ radditem(int i, int &v)
if (v > is.max) if (v > is.max)
v = is.max; v = is.max;
playsoundc(is.sound); playsoundc(is.sound);
return v;
} }
void void
realpickup(int n, dynent *d) realpickup(int n, DynamicEntity *d)
{ {
switch (ents[n].type) { switch (ents[n].type) {
case I_SHELLS: case I_SHELLS:
radditem(n, d->ammo[1]); d.ammo[1] = radditem(n, d.ammo[1]);
break; break;
case I_BULLETS: case I_BULLETS:
radditem(n, d->ammo[2]); d.ammo[2] = radditem(n, d.ammo[2]);
break; break;
case I_ROCKETS: case I_ROCKETS:
radditem(n, d->ammo[3]); d.ammo[3] = radditem(n, d.ammo[3]);
break; break;
case I_ROUNDS: case I_ROUNDS:
radditem(n, d->ammo[4]); d.ammo[4] = radditem(n, d.ammo[4]);
break; break;
case I_HEALTH: case I_HEALTH:
radditem(n, d->health); d.health = radditem(n, d.health);
break; break;
case I_BOOST: case I_BOOST:
radditem(n, d->health); d.health = radditem(n, d.health);
break; break;
case I_GREENARMOUR: case I_GREENARMOUR:
radditem(n, d->armour); d.armour = radditem(n, d.armour);
d->armourtype = A_GREEN; d.armourtype = A_GREEN;
break; break;
case I_YELLOWARMOUR: case I_YELLOWARMOUR:
radditem(n, d->armour); d.armour = radditem(n, d.armour);
d->armourtype = A_YELLOW; d.armourtype = A_YELLOW;
break; break;
case I_QUAD: case I_QUAD:
radditem(n, d->quadmillis); d.quadmillis = radditem(n, d.quadmillis);
conoutf(@"you got the quad!"); conoutf(@"you got the quad!");
break; break;
} }
@ -182,20 +184,20 @@ realpickup(int n, dynent *d)
// these functions are called when the client touches the item // these functions are called when the client touches the item
void void
additem(int i, int &v, int spawnsec) additem(int i, int v, int spawnsec)
{ {
if (v < itemstats[ents[i].type - I_SHELLS] // don't pick up if not needed
.max) // 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
addmsg(1, 3, SV_ITEMPICKUP, i, // first
m_classicsp ? 100000 addmsg(1, 3, SV_ITEMPICKUP, i, m_classicsp ? 100000 : spawnsec);
: spawnsec); // first ask the server for an ack ents[i].spawned = false;
ents[i].spawned = false; // even if someone else gets it first
} }
} }
// also used by monsters
void void
teleport(int n, dynent *d) // also used by monsters teleport(int n, DynamicEntity *d)
{ {
int e = -1, tag = ents[n].attr1, beenhere = -1; int e = -1, tag = ents[n].attr1, beenhere = -1;
for (;;) { for (;;) {
@ -207,12 +209,10 @@ teleport(int n, dynent *d) // also used by monsters
if (beenhere < 0) if (beenhere < 0)
beenhere = e; beenhere = e;
if (ents[e].attr2 == tag) { if (ents[e].attr2 == tag) {
d->o.x = ents[e].x; d.o = OFMakeVector3D(ents[e].x, ents[e].y, ents[e].z);
d->o.y = ents[e].y; d.yaw = ents[e].attr1;
d->o.z = ents[e].z; d.pitch = 0;
d->yaw = ents[e].attr1; d.vel = OFMakeVector3D(0, 0, 0);
d->pitch = 0;
d->vel.x = d->vel.y = d->vel.z = 0;
entinmap(d); entinmap(d);
playsoundc(S_TELEPORT); playsoundc(S_TELEPORT);
break; break;
@ -221,46 +221,48 @@ teleport(int n, dynent *d) // also used by monsters
} }
void void
pickup(int n, dynent *d) pickup(int n, DynamicEntity *d)
{ {
int np = 1; int np = 1;
loopv(players) if (players[i]) np++; for (id player in players)
np = np < 3 ? 4 : (np > 4 ? 2 : 3); // spawn times are dependent on if (player != [OFNull null])
// number of players np++;
// spawn times are dependent on number of players
np = np < 3 ? 4 : (np > 4 ? 2 : 3);
int ammo = np * 2; int ammo = np * 2;
switch (ents[n].type) { switch (ents[n].type) {
case I_SHELLS: case I_SHELLS:
additem(n, d->ammo[1], ammo); additem(n, d.ammo[1], ammo);
break; break;
case I_BULLETS: case I_BULLETS:
additem(n, d->ammo[2], ammo); additem(n, d.ammo[2], ammo);
break; break;
case I_ROCKETS: case I_ROCKETS:
additem(n, d->ammo[3], ammo); additem(n, d.ammo[3], ammo);
break; break;
case I_ROUNDS: case I_ROUNDS:
additem(n, d->ammo[4], ammo); additem(n, d.ammo[4], ammo);
break; break;
case I_HEALTH: case I_HEALTH:
additem(n, d->health, np * 5); additem(n, d.health, np * 5);
break; break;
case I_BOOST: case I_BOOST:
additem(n, d->health, 60); additem(n, d.health, 60);
break; break;
case I_GREENARMOUR: case I_GREENARMOUR:
// (100h/100g only absorbs 166 damage) // (100h/100g only absorbs 166 damage)
if (d->armourtype == A_YELLOW && d->armour > 66) if (d.armourtype == A_YELLOW && d.armour > 66)
break; break;
additem(n, d->armour, 20); additem(n, d.armour, 20);
break; break;
case I_YELLOWARMOUR: case I_YELLOWARMOUR:
additem(n, d->armour, 20); additem(n, d.armour, 20);
break; break;
case I_QUAD: case I_QUAD:
additem(n, d->quadmillis, 60); additem(n, d.quadmillis, 60);
break; break;
case CARROT: case CARROT:
@ -286,8 +288,8 @@ pickup(int n, dynent *d)
lastjumppad = lastmillis; lastjumppad = lastmillis;
OFVector3D v = OFMakeVector3D((int)(char)ents[n].attr3 / 10.0f, OFVector3D v = OFMakeVector3D((int)(char)ents[n].attr3 / 10.0f,
(int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f); (int)(char)ents[n].attr2 / 10.0f, ents[n].attr1 / 10.0f);
player1->vel.z = 0; player1.vel = OFMakeVector3D(player1.vel.x, player1.vel.y, 0);
vadd(player1->vel, v); vadd(player1.vel, v);
playsoundc(S_JUMPPAD); playsoundc(S_JUMPPAD);
break; break;
} }
@ -309,8 +311,8 @@ checkitems()
if (OUTBORD(e.x, e.y)) if (OUTBORD(e.x, e.y))
continue; continue;
OFVector3D v = OFMakeVector3D( OFVector3D v = OFMakeVector3D(
e.x, e.y, (float)S(e.x, e.y)->floor + player1->eyeheight); e.x, e.y, (float)S(e.x, e.y)->floor + player1.eyeheight);
vdist(dist, t, player1->o, v); vdist(dist, t, player1.o, v);
if (dist < (e.type == TELEPORT ? 4 : 2.5)) if (dist < (e.type == TELEPORT ? 4 : 2.5))
pickup(i, player1); pickup(i, player1);
} }
@ -319,8 +321,8 @@ checkitems()
void void
checkquad(int time) checkquad(int time)
{ {
if (player1->quadmillis && (player1->quadmillis -= time) < 0) { if (player1.quadmillis && (player1.quadmillis -= time) < 0) {
player1->quadmillis = 0; player1.quadmillis = 0;
playsoundc(S_PUPOUT); playsoundc(S_PUPOUT);
conoutf(@"quad damage is over"); conoutf(@"quad damage is over");
} }

View file

@ -3,6 +3,7 @@ executable('client',
'Alias.m', 'Alias.m',
'Command.mm', 'Command.mm',
'Cube.mm', 'Cube.mm',
'DynamicEntity.mm',
'Identifier.m', 'Identifier.m',
'KeyMapping.m', 'KeyMapping.m',
'MD2.mm', 'MD2.mm',

View file

@ -2,22 +2,27 @@
#include "cube.h" #include "cube.h"
dvector monsters; #import "DynamicEntity.h"
int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
static OFMutableArray<DynamicEntity *> *monsters;
static int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill)); VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill));
dvector & OFArray<DynamicEntity *> *
getmonsters() getmonsters()
{ {
return monsters; return monsters;
} }
// for savegames
void void
restoremonsterstate() restoremonsterstate()
{ {
loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++; for (DynamicEntity *monster in monsters)
} // for savegames if (monster.state == CS_DEAD)
numkilled++;
}
#define TOTMFREQ 13 #define TOTMFREQ 13
#define NUMMONSTERTYPES 8 #define NUMMONSTERTYPES 8
@ -49,40 +54,43 @@ monstertypes[NUMMONSTERTYPES] = {
@"a goblin", @"monster/goblin" }, @"a goblin", @"monster/goblin" },
}; };
dynent * DynamicEntity *
basicmonster(int type, int yaw, int state, int trigger, int move) basicmonster(int type, int yaw, int state, int trigger, int move)
{ {
if (type >= NUMMONSTERTYPES) { if (type >= NUMMONSTERTYPES) {
conoutf(@"warning: unknown monster in spawn: %d", type); conoutf(@"warning: unknown monster in spawn: %d", type);
type = 0; type = 0;
} }
dynent *m = newdynent(); DynamicEntity *m = newdynent();
monstertype *t = &monstertypes[m->mtype = type]; monstertype *t = &monstertypes[(m.mtype = type)];
m->eyeheight = 2.0f; m.eyeheight = 2.0f;
m->aboveeye = 1.9f; m.aboveeye = 1.9f;
m->radius *= t->bscale / 10.0f; m.radius *= t->bscale / 10.0f;
m->eyeheight *= t->bscale / 10.0f; m.eyeheight *= t->bscale / 10.0f;
m->aboveeye *= t->bscale / 10.0f; m.aboveeye *= t->bscale / 10.0f;
m->monsterstate = state; m.monsterstate = state;
if (state != M_SLEEP) if (state != M_SLEEP)
spawnplayer(m); spawnplayer(m);
m->trigger = lastmillis + trigger; m.trigger = lastmillis + trigger;
m->targetyaw = m->yaw = (float)yaw; m.targetyaw = m.yaw = (float)yaw;
m->move = move; m.move = move;
m->enemy = player1; m.enemy = player1;
m->gunselect = t->gun; m.gunselect = t->gun;
m->maxspeed = (float)t->speed; m.maxspeed = (float)t->speed;
m->health = t->health; m.health = t->health;
m->armour = 0; m.armour = 0;
loopi(NUMGUNS) m->ammo[i] = 10000; loopi(NUMGUNS) m.ammo[i] = 10000;
m->pitch = 0; m.pitch = 0;
m->roll = 0; m.roll = 0;
m->state = CS_ALIVE; m.state = CS_ALIVE;
m->anger = 0; m.anger = 0;
@autoreleasepool { m.name = t->name;
strcpy_s(m->name, t->name.UTF8String);
} if (monsters == nil)
monsters.add(m); monsters = [[OFMutableArray alloc] init];
[monsters addObject:m];
return m; return m;
} }
@ -99,12 +107,12 @@ spawnmonster() // spawn a random monster according to freq distribution in DMSP
basicmonster(type, rnd(360), M_SEARCH, 1000, 1); 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 void
monsterclear() // called after map start of when toggling edit mode to monsterclear()
// reset/spawn all monsters to initial state
{ {
loopv(monsters) free(monsters[i]); [monsters removeAllObjects];
monsters.setsize(0);
numkilled = 0; numkilled = 0;
monstertotal = 0; monstertotal = 0;
spawnremain = 0; spawnremain = 0;
@ -115,20 +123,18 @@ monsterclear() // called after map start of when toggling edit mode to
mtimestart = lastmillis; mtimestart = lastmillis;
loopv(ents) if (ents[i].type == MONSTER) loopv(ents) if (ents[i].type == MONSTER)
{ {
dynent *m = basicmonster( DynamicEntity *m = basicmonster(
ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0); ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0);
m->o.x = ents[i].x; m.o = OFMakeVector3D(ents[i].x, ents[i].y, ents[i].z);
m->o.y = ents[i].y;
m->o.z = ents[i].z;
entinmap(m); entinmap(m);
monstertotal++; monstertotal++;
} }
} }
} }
// height-correct line of sight for monster shooting/seeing
bool bool
los(float lx, float ly, float lz, float bx, float by, float bz, los(float lx, float ly, float lz, float bx, float by, float bz, OFVector3D &v)
OFVector3D &v) // height-correct line of sight for monster shooting/seeing
{ {
if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by)) if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by))
return false; return false;
@ -164,11 +170,11 @@ los(float lx, float ly, float lz, float bx, float by, float bz,
} }
bool bool
enemylos(dynent *m, OFVector3D &v) enemylos(DynamicEntity *m, OFVector3D &v)
{ {
v = m->o; v = m.o;
return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y, return los(
m->enemy->o.z, v); 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 // monster AI is sequenced using transitions: they are in a particular state
@ -178,73 +184,72 @@ enemylos(dynent *m, OFVector3D &v)
// parametrized by difficulty level (skill), faster transitions means quicker // parametrized by difficulty level (skill), faster transitions means quicker
// decision making means tougher AI. // decision making means tougher AI.
// n = at skill 0, n/2 = at skill 10, r = added random factor
void void
transition(dynent *m, int state, int moving, int n, transition(DynamicEntity *m, int state, int moving, int n, int r)
int r) // n = at skill 0, n/2 = at skill 10, r = added random factor
{ {
m->monsterstate = state; m.monsterstate = state;
m->move = moving; m.move = moving;
n = n * 130 / 100; n = n * 130 / 100;
m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1); m.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
} }
void void
normalise(dynent *m, float angle) normalise(DynamicEntity *m, float angle)
{ {
while (m->yaw < angle - 180.0f) while (m.yaw < angle - 180.0f)
m->yaw += 360.0f; m.yaw += 360.0f;
while (m->yaw > angle + 180.0f) while (m.yaw > angle + 180.0f)
m->yaw -= 360.0f; m.yaw -= 360.0f;
} }
// main AI thinking routine, called every frame for every monster
void void
monsteraction( monsteraction(DynamicEntity *m)
dynent *m) // main AI thinking routine, called every frame for every monster
{ {
if (m->enemy->state == CS_DEAD) { if (m.enemy.state == CS_DEAD) {
m->enemy = player1; m.enemy = player1;
m->anger = 0; m.anger = 0;
} }
normalise(m, m->targetyaw); normalise(m, m.targetyaw);
if (m->targetyaw > m->yaw) // slowly turn monster towards his target // slowly turn monster towards his target
{ if (m.targetyaw > m.yaw) {
m->yaw += curtime * 0.5f; m.yaw += curtime * 0.5f;
if (m->targetyaw < m->yaw) if (m.targetyaw < m.yaw)
m->yaw = m->targetyaw; m.yaw = m.targetyaw;
} else { } else {
m->yaw -= curtime * 0.5f; m.yaw -= curtime * 0.5f;
if (m->targetyaw > m->yaw) if (m.targetyaw > m.yaw)
m->yaw = m->targetyaw; m.yaw = m.targetyaw;
} }
vdist(disttoenemy, vectoenemy, m->o, m->enemy->o); vdist(disttoenemy, vectoenemy, m.o, m.enemy.o);
m->pitch = atan2(m->enemy->o.z - m->o.z, disttoenemy) * 180 / PI; m.pitch = atan2(m.enemy.o.z - m.o.z, disttoenemy) * 180 / PI;
// special case: if we run into scenery // special case: if we run into scenery
if (m->blocked) { if (m.blocked) {
m->blocked = false; m.blocked = false;
// try to jump over obstackle (rare) // try to jump over obstackle (rare)
if (!rnd(20000 / monstertypes[m->mtype].speed)) if (!rnd(20000 / monstertypes[m.mtype].speed))
m->jumpnext = true; m.jumpnext = true;
// search for a way around (common) // search for a way around (common)
else if (m->trigger < lastmillis && else if (m.trigger < lastmillis &&
(m->monsterstate != M_HOME || !rnd(5))) { (m.monsterstate != M_HOME || !rnd(5))) {
// patented "random walk" AI pathfinding (tm) ;) // patented "random walk" AI pathfinding (tm) ;)
m->targetyaw += 180 + rnd(180); m.targetyaw += 180 + rnd(180);
transition(m, M_SEARCH, 1, 400, 1000); transition(m, M_SEARCH, 1, 400, 1000);
} }
} }
float enemyyaw = float enemyyaw =
-(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y) / PI * -(float)atan2(m.enemy.o.x - m.o.x, m.enemy.o.y - m.o.y) / PI * 180 +
180 +
180; 180;
switch (m->monsterstate) { switch (m.monsterstate) {
case M_PAIN: case M_PAIN:
case M_ATTACKING: case M_ATTACKING:
case M_SEARCH: case M_SEARCH:
if (m->trigger < lastmillis) if (m.trigger < lastmillis)
transition(m, M_HOME, 1, 100, 200); transition(m, M_HOME, 1, 100, 200);
break; break;
@ -255,54 +260,53 @@ monsteraction(
if (editmode || !enemylos(m, target)) if (editmode || !enemylos(m, target))
return; // skip running physics return; // skip running physics
normalise(m, enemyyaw); 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 if (disttoenemy < 8 // the better the angle to the player, the
// further the monster can see/hear // further the monster can see/hear
|| (disttoenemy < 16 && angle < 135) || || (disttoenemy < 16 && angle < 135) ||
(disttoenemy < 32 && angle < 90) || (disttoenemy < 32 && angle < 90) ||
(disttoenemy < 64 && angle < 45) || angle < 10) { (disttoenemy < 64 && angle < 45) || angle < 10) {
transition(m, M_HOME, 1, 500, 200); 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; break;
} }
case M_AIMING: // this state is the delay between wanting to shoot and case M_AIMING:
// actually firing // this state is the delay between wanting to shoot and actually
if (m->trigger < lastmillis) { // firing
m->lastaction = 0; if (m.trigger < lastmillis) {
m->attacking = true; m.lastaction = 0;
shoot(m, m->attacktarget); m.attacking = true;
shoot(m, m.attacktarget);
transition(m, M_ATTACKING, 0, 600, 0); transition(m, M_ATTACKING, 0, 600, 0);
} }
break; break;
case M_HOME: // monster has visual contact, heads straight for player case M_HOME:
// and may want to shoot at any time // monster has visual contact, heads straight for player and
m->targetyaw = enemyyaw; // may want to shoot at any time
if (m->trigger < lastmillis) { m.targetyaw = enemyyaw;
if (m.trigger < lastmillis) {
OFVector3D target; OFVector3D target;
if (!enemylos( if (!enemylos(m, target)) {
m, target)) // no visual contact anymore, let // no visual contact anymore, let monster get
// monster get as close as possible // as close as possible then search for player
// then search for player
{
transition(m, M_HOME, 1, 800, 500); transition(m, M_HOME, 1, 800, 500);
} else // the closer the monster is the more likely he } else {
// the closer the monster is the more likely he
// wants to shoot // wants to shoot
{
if (!rnd((int)disttoenemy / 3 + 1) && if (!rnd((int)disttoenemy / 3 + 1) &&
m->enemy->state == m.enemy.state == CS_ALIVE) {
CS_ALIVE) // get ready to fire // get ready to fire
{ m.attacktarget = target;
m->attacktarget = target;
transition(m, M_AIMING, 0, transition(m, M_AIMING, 0,
monstertypes[m->mtype].lag, 10); monstertypes[m.mtype].lag, 10);
} else // track player some more } else
{ // track player some more
transition(m, M_HOME, 1, transition(m, M_HOME, 1,
monstertypes[m->mtype].rate, 0); monstertypes[m.mtype].rate, 0);
}
} }
} }
break; break;
@ -312,38 +316,40 @@ monsteraction(
} }
void void
monsterpain(dynent *m, int damage, dynent *d) monsterpain(DynamicEntity *m, int damage, DynamicEntity *d)
{ {
// a monster hit us // a monster hit us
if (d->monsterstate) { if (d.monsterstate) {
// guard for RL guys shooting themselves :) // guard for RL guys shooting themselves :)
if (m != d) { if (m != d) {
// don't attack straight away, first get angry // don't attack straight away, first get angry
m->anger++; m.anger++;
int anger = int anger = m.mtype == d.mtype ? m.anger / 2 : m.anger;
m->mtype == d->mtype ? m->anger / 2 : m->anger; if (anger >= monstertypes[m.mtype].loyalty)
if (anger >= monstertypes[m->mtype].loyalty)
// monster infight if very angry // monster infight if very angry
m->enemy = d; m.enemy = d;
} }
} else { } else {
// player hit us // player hit us
m->anger = 0; m.anger = 0;
m->enemy = d; m.enemy = d;
} }
// in this state monster won't attack // in this state monster won't attack
transition(m, M_PAIN, 0, monstertypes[m->mtype].pain, 200); transition(m, M_PAIN, 0, monstertypes[m.mtype].pain, 200);
if ((m->health -= damage) <= 0) { if ((m.health -= damage) <= 0) {
m->state = CS_DEAD; m.state = CS_DEAD;
m->lastaction = lastmillis; m.lastaction = lastmillis;
numkilled++; numkilled++;
player1->frags = numkilled; player1.frags = numkilled;
playsound(monstertypes[m->mtype].diesound, &m->o); OFVector3D loc = m.o;
playsound(monstertypes[m.mtype].diesound, &loc);
int remain = monstertotal - numkilled; int remain = monstertotal - numkilled;
if (remain > 0 && remain <= 5) if (remain > 0 && remain <= 5)
conoutf(@"only %d monster(s) remaining", remain); conoutf(@"only %d monster(s) remaining", remain);
} else } else {
playsound(monstertypes[m->mtype].painsound, &m->o); OFVector3D loc = m.o;
playsound(monstertypes[m.mtype].painsound, &loc);
}
} }
void void
@ -370,8 +376,8 @@ monsterthink()
if (monstertotal && !spawnremain && numkilled == monstertotal) if (monstertotal && !spawnremain && numkilled == monstertotal)
endsp(true); endsp(true);
loopv(ents) // equivalent of player entity touch, but only teleports are // equivalent of player entity touch, but only teleports are used
// used loopv(ents)
{ {
entity &e = ents[i]; entity &e = ents[i];
if (e.type != TELEPORT) if (e.type != TELEPORT)
@ -380,33 +386,32 @@ monsterthink()
continue; continue;
OFVector3D v = OFVector3D v =
OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor); OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor);
loopv(monsters) for (DynamicEntity *monster in monsters) {
{ if (monster.state == CS_DEAD) {
if (monsters[i]->state == CS_DEAD) { if (lastmillis - monster.lastaction < 2000) {
if (lastmillis - monsters[i]->lastaction < monster.move = 0;
2000) { moveplayer(monster, 1, false);
monsters[i]->move = 0;
moveplayer(monsters[i], 1, false);
} }
} else { } else {
v.z += monsters[i]->eyeheight; v.z += monster.eyeheight;
vdist(dist, t, monsters[i]->o, v); vdist(dist, t, monster.o, v);
v.z -= monsters[i]->eyeheight; v.z -= monster.eyeheight;
if (dist < 4) if (dist < 4)
teleport( teleport((int)(&e - &ents[0]), monster);
(int)(&e - &ents[0]), monsters[i]);
} }
} }
} }
loopv(monsters) if (monsters[i]->state == CS_ALIVE) for (DynamicEntity *monster in monsters)
monsteraction(monsters[i]); if (monster.state == CS_ALIVE)
monsteraction(monster);
} }
void void
monsterrender() monsterrender()
{ {
loopv(monsters) renderclient(monsters[i], false, for (DynamicEntity *monster in monsters)
monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5, renderclient(monster, false,
monstertypes[monsters[i]->mtype].mscale / 10.0f); monstertypes[monster.mtype].mdlname, monster.mtype == 5,
monstertypes[monster.mtype].mscale / 10.0f);
} }

View file

@ -6,27 +6,29 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#import "MapModelInfo.h" #import "MapModelInfo.h"
// collide with player or monster
bool bool
plcollide(dynent *d, dynent *o, float &headspace, float &hi, plcollide(
float &lo) // collide with player or monster DynamicEntity *d, DynamicEntity *o, float &headspace, float &hi, float &lo)
{ {
if (o->state != CS_ALIVE) if (o.state != CS_ALIVE)
return true; return true;
const float r = o->radius + d->radius; 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 (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 (d.o.z - d.eyeheight < o.o.z - o.eyeheight) {
if (o->o.z - o->eyeheight < hi) if (o.o.z - o.eyeheight < hi)
hi = o->o.z - o->eyeheight - 1; hi = o.o.z - o.eyeheight - 1;
} else if (o->o.z + o->aboveeye > lo) } else if (o.o.z + o.aboveeye > lo)
lo = o->o.z + o->aboveeye + 1; lo = o.o.z + o.aboveeye + 1;
if (fabs(o->o.z - d->o.z) < o->aboveeye + d->eyeheight) if (fabs(o.o.z - d.o.z) < o.aboveeye + d.eyeheight)
return false; return false;
if (d->monsterstate) if (d.monsterstate)
return false; // hack 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) if (headspace < 0)
headspace = 10; headspace = 10;
} }
@ -54,7 +56,7 @@ cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by,
} }
void 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) loopv(ents)
{ {
@ -64,11 +66,11 @@ mmcollide(dynent *d, float &hi, float &lo) // collide with a mapmodel
MapModelInfo *mmi = getmminfo(e.attr2); MapModelInfo *mmi = getmminfo(e.attr2);
if (mmi == nil || !mmi.h) if (mmi == nil || !mmi.h)
continue; continue;
const float r = mmi.rad + d->radius; const float r = mmi.rad + d.radius;
if (fabs(e.x - d->o.x) < r && fabs(e.y - d->o.y) < r) { if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) {
float mmz = float mmz =
(float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3); (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) if (mmz < hi)
hi = mmz; hi = mmz;
} else if (mmz + mmi.h > lo) } else if (mmz + mmi.h > lo)
@ -83,27 +85,26 @@ mmcollide(dynent *d, float &hi, float &lo) // collide with a mapmodel
// current mini-timestep // current mini-timestep
bool bool
collide(dynent *d, bool spawn, float drop, float rise) collide(DynamicEntity *d, bool spawn, float drop, float rise)
{ {
const float fx1 = // figure out integer cube rectangle this entity covers in map
d->o.x - d->radius; // figure out integer cube rectangle this entity const float fx1 = d.o.x - d.radius;
// covers in map const float fy1 = d.o.y - d.radius;
const float fy1 = d->o.y - d->radius; const float fx2 = d.o.x + d.radius;
const float fx2 = d->o.x + d->radius; const float fy2 = d.o.y + d.radius;
const float fy2 = d->o.y + d->radius;
const int x1 = fast_f2nat(fx1); const int x1 = fast_f2nat(fx1);
const int y1 = fast_f2nat(fy1); const int y1 = fast_f2nat(fy1);
const int x2 = fast_f2nat(fx2); const int x2 = fast_f2nat(fx2);
const int y2 = fast_f2nat(fy2); const int y2 = fast_f2nat(fy2);
float hi = 127, lo = -128; float hi = 127, lo = -128;
float minfloor = (d->monsterstate && !spawn && d->health > 100) // big monsters are afraid of heights, unless angry :)
? d->o.z - d->eyeheight - 4.5f float minfloor = (d.monsterstate && !spawn && d.health > 100)
: -1000.0f; // big monsters are afraid of heights, ? d.o.z - d.eyeheight - 4.5f
// unless angry :) : -1000.0f;
for (int x = x1; x <= x2; x++) 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)) if (OUTBORD(x, y))
return false; return false;
sqr *s = S(x, y); sqr *s = S(x, y);
@ -156,55 +157,62 @@ collide(dynent *d, bool spawn, float drop, float rise)
return false; return false;
} }
if (hi - lo < d->eyeheight + d->aboveeye) if (hi - lo < d.eyeheight + d.aboveeye)
return false; return false;
float headspace = 10; float headspace = 10;
loopv(players) // collide with other players for (id player in players) {
{ if (player == [OFNull null] || player == d)
dynent *o = players[i];
if (!o || o == d)
continue; continue;
if (!plcollide(d, o, headspace, hi, lo)) if (!plcollide(d, player, headspace, hi, lo))
return false; return false;
} }
if (d != player1) if (d != player1)
if (!plcollide(d, player1, headspace, hi, lo)) if (!plcollide(d, player1, headspace, hi, lo))
return false; return false;
dvector &v = getmonsters();
// this loop can be a performance bottleneck with many monster on a slow // this loop can be a performance bottleneck with many monster on a slow
// cpu, should replace with a blockmap but seems mostly fast enough // 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] && for (DynamicEntity *monster in getmonsters())
!plcollide(d, v[i], headspace, hi, lo)) return false; if (!vreject(d.o, monster.o, 7.0f) && d != monster &&
!plcollide(d, monster, headspace, hi, lo))
return false;
headspace -= 0.01f; headspace -= 0.01f;
mmcollide(d, hi, lo); // collide with map models mmcollide(d, hi, lo); // collide with map models
if (spawn) { if (spawn) {
d->o.z = lo + d->eyeheight; // just drop to floor (sideeffect) // just drop to floor (sideeffect)
d->onfloor = true; d.o = OFMakeVector3D(d.o.x, d.o.y, lo + d.eyeheight);
d.onfloor = true;
} else { } 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) {
if (space > -0.01) 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) 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 else
return false; return false;
} else { } else
d->o.z -= min(min(drop, space), headspace); // gravity // 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) {
if (space2 < -0.1) if (space2 < -0.1)
return false; // hack alert! return false; // hack alert!
d->o.z = hi - d->aboveeye; // glue to ceiling // glue to ceiling
d->vel.z = 0; // cancel out jumping velocity 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; return true;
} }
@ -237,125 +245,126 @@ physicsframe() // optimally schedule physics frames inside the graphics frames
// multiplayer prediction) local is false for multiplayer prediction // multiplayer prediction) local is false for multiplayer prediction
void 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 water = hdr.waterlevel > pl.o.z - 0.5f;
const bool floating = (editmode && local) || pl->state == CS_EDITING; const bool floating = (editmode && local) || pl.state == CS_EDITING;
OFVector3D d; // vector of direction we ideally want to move in OFVector3D d; // vector of direction we ideally want to move in
d.x = (float)(pl->move * cos(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.y = (float)(pl.move * sin(rad(pl.yaw - 90)));
d.z = 0; d.z = 0;
if (floating || water) { if (floating || water) {
d.x *= (float)cos(rad(pl->pitch)); d.x *= (float)cos(rad(pl.pitch));
d.y *= (float)cos(rad(pl->pitch)); d.y *= (float)cos(rad(pl.pitch));
d.z = (float)(pl->move * sin(rad(pl->pitch))); d.z = (float)(pl.move * sin(rad(pl.pitch)));
} }
d.x += (float)(pl->strafe * cos(rad(pl->yaw - 180))); d.x += (float)(pl.strafe * cos(rad(pl.yaw - 180)));
d.y += (float)(pl->strafe * sin(rad(pl->yaw - 180))); d.y += (float)(pl.strafe * sin(rad(pl.yaw - 180)));
const float speed = const float speed = curtime / (water ? 2000.0f : 1000.0f) * pl.maxspeed;
curtime / (water ? 2000.0f : 1000.0f) * pl->maxspeed;
const float friction = 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; const float fpsfric = friction / curtime * 20.0f;
vmul(pl->vel, fpsfric - 1); // slowly apply friction and direction to // slowly apply friction and direction to
// velocity, gives a smooth movement // velocity, gives a smooth movement
vadd(pl->vel, d); vmul(pl.vel, fpsfric - 1);
vdiv(pl->vel, fpsfric); vadd(pl.vel, d);
d = pl->vel; vdiv(pl.vel, fpsfric);
d = pl.vel;
vmul(d, speed); // d is now frametime based velocity vector vmul(d, speed); // d is now frametime based velocity vector
pl->blocked = false; pl.blocked = false;
pl->moving = true; pl.moving = true;
if (floating) // just apply velocity if (floating) {
{ // just apply velocity
vadd(pl->o, d); vadd(pl.o, d);
if (pl->jumpnext) { if (pl.jumpnext) {
pl->jumpnext = false; pl.jumpnext = false;
pl->vel.z = 2; pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 2);
} }
} else // apply velocity with collision } else {
{ // apply velocity with collision
if (pl->onfloor || water) { if (pl.onfloor || water) {
if (pl->jumpnext) { if (pl.jumpnext) {
pl->jumpnext = false; pl.jumpnext = false;
pl->vel.z = 1.7f; // physics impulse upwards // physics impulse upwards
pl.vel =
OFMakeVector3D(pl.vel.x, pl.vel.y, 1.7);
// dampen velocity change even harder, gives // dampen velocity change even harder, gives
// correct water feel // correct water feel
if (water) { if (water)
pl->vel.x /= 8; pl.vel = OFMakeVector3D(pl.vel.x / 8,
pl->vel.y /= 8; pl.vel.y / 8, pl.vel.z);
}
if (local) if (local)
playsoundc(S_JUMP); playsoundc(S_JUMP);
else if (pl->monsterstate) else if (pl.monsterstate) {
playsound(S_JUMP, &pl->o); OFVector3D loc = pl.o;
} else if (pl->timeinair > 800) { playsound(S_JUMP, &loc);
}
} else if (pl.timeinair > 800) {
// if we land after long time must have been a // if we land after long time must have been a
// high jump, make thud sound // high jump, make thud sound
if (local) if (local)
playsoundc(S_LAND); playsoundc(S_LAND);
else if (pl->monsterstate) else if (pl.monsterstate) {
playsound(S_LAND, &pl->o); 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 gravity = 20;
const float f = 1.0f / moveres; const float f = 1.0f / moveres;
// incorrect, but works fine // 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 // float slowly down in water
if (water) { if (water) {
dropf = 5; dropf = 5;
pl->timeinair = 0; pl.timeinair = 0;
} }
const float drop = dropf * curtime / gravity / 100 / // at high fps, gravity kicks in too fast
moveres; // at high fps, gravity kicks in too fast const float drop = dropf * curtime / gravity / 100 / moveres;
const float rise = speed / moveres / // extra smoothness when lifting up stairs
1.2f; // extra smoothness when lifting up stairs const float rise = speed / moveres / 1.2f;
loopi(moveres) // discrete steps collision detection & sliding loopi(moveres) // discrete steps collision detection & sliding
{ {
// try move forward // try move forward
pl->o.x += f * d.x; pl.o = OFMakeVector3D(pl.o.x + f * d.x,
pl->o.y += f * d.y; pl.o.y + f * d.y, pl.o.z + f * d.z);
pl->o.z += f * d.z;
if (collide(pl, false, drop, rise)) if (collide(pl, false, drop, rise))
continue; continue;
// player stuck, try slide along y axis // player stuck, try slide along y axis
pl->blocked = true; pl.blocked = true;
pl->o.x -= f * d.x; pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
if (collide(pl, false, drop, rise)) { if (collide(pl, false, drop, rise)) {
d.x = 0; d.x = 0;
continue; continue;
} }
pl->o.x += f * d.x;
// still stuck, try x axis // 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)) { if (collide(pl, false, drop, rise)) {
d.y = 0; d.y = 0;
continue; continue;
} }
pl->o.y += f * d.y;
// try just dropping down // try just dropping down
pl->moving = false; pl.moving = false;
pl->o.x -= f * d.x; pl.o = OFMakeVector3D(pl.o.x - f * d.x, pl.o.y, pl.o.z);
pl->o.y -= f * d.y;
if (collide(pl, false, drop, rise)) { if (collide(pl, false, drop, rise)) {
d.y = d.x = 0; d.y = d.x = 0;
continue; continue;
} }
pl->o.z -= f * d.z; pl.o = OFMakeVector3D(pl.o.x, pl.o.y, pl.o.z - f * d.z);
break; break;
} }
} }
@ -363,39 +372,42 @@ moveplayer(dynent *pl, int moveres, bool local, int curtime)
// detect wether player is outside map, used for skipping zbuffer clear // detect wether player is outside map, used for skipping zbuffer clear
// mostly // mostly
if (pl->o.x < 0 || pl->o.x >= ssize || pl->o.y < 0 || pl->o.y > ssize) { if (pl.o.x < 0 || pl.o.x >= ssize || pl.o.y < 0 || pl.o.y > ssize)
pl->outsidemap = true; pl.outsidemap = true;
} else { else {
sqr *s = S((int)pl->o.x, (int)pl->o.y); sqr *s = S((int)pl.o.x, (int)pl.o.y);
pl->outsidemap = SOLID(s) || pl.outsidemap = SOLID(s) ||
pl->o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) || pl.o.z < s->floor - (s->type == FHF ? s->vdelta / 4 : 0) ||
pl->o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0); pl.o.z > s->ceil + (s->type == CHF ? s->vdelta / 4 : 0);
} }
// automatically apply smooth roll when strafing // automatically apply smooth roll when strafing
if (pl->strafe == 0) { if (pl.strafe == 0)
pl->roll = pl->roll / (1 + (float)sqrt((float)curtime) / 25); pl.roll = pl.roll / (1 + (float)sqrt((float)curtime) / 25);
} else { else {
pl->roll += pl->strafe * curtime / -30.0f; pl.roll += pl.strafe * curtime / -30.0f;
if (pl->roll > maxroll) if (pl.roll > maxroll)
pl->roll = (float)maxroll; pl.roll = (float)maxroll;
if (pl->roll < -maxroll) if (pl.roll < -maxroll)
pl->roll = (float)-maxroll; pl.roll = (float)-maxroll;
} }
// play sounds on water transitions // play sounds on water transitions
if (!pl->inwater && water) { if (!pl.inwater && water) {
playsound(S_SPLASH2, &pl->o); OFVector3D loc = pl.o;
pl->vel.z = 0; playsound(S_SPLASH2, &loc);
} else if (pl->inwater && !water) pl.vel = OFMakeVector3D(pl.vel.x, pl.vel.y, 0);
playsound(S_SPLASH1, &pl->o); } else if (pl.inwater && !water) {
pl->inwater = water; OFVector3D loc = pl.o;
playsound(S_SPLASH1, &loc);
}
pl.inwater = water;
} }
void void
moveplayer(dynent *pl, int moveres, bool local) moveplayer(DynamicEntity *pl, int moveres, bool local)
{ {
loopi(physicsrepeat) moveplayer(pl, moveres, local, loopi(physicsrepeat) moveplayer(pl, moveres, local,
i ? curtime / physicsrepeat i ? curtime / physicsrepeat

View file

@ -78,7 +78,7 @@ extern bool multiplayer();
extern bool allowedittoggle(); extern bool allowedittoggle();
extern void sendpackettoserv(void *packet); extern void sendpackettoserv(void *packet);
extern void gets2c(); extern void gets2c();
extern void c2sinfo(dynent *d); extern void c2sinfo(DynamicEntity *d);
extern void neterr(OFString *s); extern void neterr(OFString *s);
extern void initclientnet(); extern void initclientnet();
extern bool netmapstart(); extern bool netmapstart();
@ -87,26 +87,27 @@ extern void changemapserv(OFString *name, int mode);
extern void writeclientinfo(OFStream *stream); extern void writeclientinfo(OFStream *stream);
// clientgame // clientgame
extern void initPlayers();
extern void mousemove(int dx, int dy); extern void mousemove(int dx, int dy);
extern void updateworld(int millis); extern void updateworld(int millis);
extern void startmap(OFString *name); extern void startmap(OFString *name);
extern void changemap(OFString *name); extern void changemap(OFString *name);
extern void initclient(); extern void initclient();
extern void spawnplayer(dynent *d); extern void spawnplayer(DynamicEntity *d);
extern void selfdamage(int damage, int actor, dynent *act); extern void selfdamage(int damage, int actor, DynamicEntity *act);
extern dynent *newdynent(); extern DynamicEntity *newdynent();
extern OFString *getclientmap(); extern OFString *getclientmap();
extern OFString *modestr(int n); extern OFString *modestr(int n);
extern void zapdynent(dynent *&d); extern DynamicEntity *getclient(int cn);
extern dynent *getclient(int cn); extern void setclient(int cn, id client);
extern void timeupdate(int timeremain); extern void timeupdate(int timeremain);
extern void resetmovement(dynent *d); extern void resetmovement(DynamicEntity *d);
extern void fixplayer1range(); extern void fixplayer1range();
// clientextras // clientextras
extern void renderclients(); extern void renderclients();
extern void renderclient( 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); void showscores(bool on);
extern void renderscores(); extern void renderscores();
@ -126,7 +127,7 @@ extern entity *newentity(
// worldlight // worldlight
extern void calclight(); extern void calclight();
extern void dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, extern void dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach,
int strength, dynent *owner); int strength, DynamicEntity *owner);
extern void cleardlights(); extern void cleardlights();
extern block *blockcopy(block &b); extern block *blockcopy(block &b);
extern void blockpaste(block &b); extern void blockpaste(block &b);
@ -191,13 +192,13 @@ extern void incomingdemodata(uchar *buf, int len, bool extras = false);
extern void demoplaybackstep(); extern void demoplaybackstep();
extern void stop(); extern void stop();
extern void stopifrecording(); extern void stopifrecording();
extern void demodamage(int damage, OFVector3D &o); extern void demodamage(int damage, const OFVector3D &o);
extern void demoblend(int damage); extern void demoblend(int damage);
// physics // physics
extern void moveplayer(dynent *pl, int moveres, bool local); extern void moveplayer(DynamicEntity *pl, int moveres, bool local);
extern bool collide(dynent *d, bool spawn, float drop, float rise); extern bool collide(DynamicEntity *d, bool spawn, float drop, float rise);
extern void entinmap(dynent *d); extern void entinmap(DynamicEntity *d);
extern void setentphysics(int mml, int mmr); extern void setentphysics(int mml, int mmr);
extern void physicsframe(); extern void physicsframe();
@ -237,9 +238,9 @@ extern ENetPacket *recvmap(int n);
// weapon // weapon
extern void selectgun(int a = -1, int b = -1, int c = -1); extern void selectgun(int a = -1, int b = -1, int c = -1);
extern void shoot(dynent *d, OFVector3D &to); extern void shoot(DynamicEntity *d, const OFVector3D &to);
extern void shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d = 0, extern void shootv(int gun, OFVector3D &from, OFVector3D &to,
bool local = false); DynamicEntity *d = 0, bool local = false);
extern void createrays(OFVector3D &from, OFVector3D &to); extern void createrays(OFVector3D &from, OFVector3D &to);
extern void moveprojectiles(float time); extern void moveprojectiles(float time);
extern void projreset(); extern void projreset();
@ -251,8 +252,8 @@ extern void monsterclear();
extern void restoremonsterstate(); extern void restoremonsterstate();
extern void monsterthink(); extern void monsterthink();
extern void monsterrender(); extern void monsterrender();
extern dvector &getmonsters(); extern OFArray<DynamicEntity *> *getmonsters();
extern void monsterpain(dynent *m, int damage, dynent *d); extern void monsterpain(DynamicEntity *m, int damage, DynamicEntity *d);
extern void endsp(bool allkilled); extern void endsp(bool allkilled);
// entities // entities
@ -260,11 +261,11 @@ extern void renderents();
extern void putitems(uchar *&p); extern void putitems(uchar *&p);
extern void checkquad(int time); extern void checkquad(int time);
extern void checkitems(); extern void checkitems();
extern void realpickup(int n, dynent *d); extern void realpickup(int n, DynamicEntity *d);
extern void renderentities(); extern void renderentities();
extern void resetspawns(); extern void resetspawns();
extern void setspawn(uint i, bool on); 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); extern void baseammo(int gun);
// rndmap // rndmap

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
void void
line(int x1, int y1, float z1, int x2, int y2, float z2) line(int x1, int y1, float z1, int x2, int y2, float z2)
{ {
@ -329,7 +331,7 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
readmatrices(); readmatrices();
if (editmode) { if (editmode) {
if (cursordepth == 1.0f) if (cursordepth == 1.0f)
worldpos = player1->o; worldpos = player1.o;
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
cursorupdate(); cursorupdate();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
@ -381,11 +383,11 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
glBegin(GL_QUADS); glBegin(GL_QUADS);
glColor3ub(255, 255, 255); glColor3ub(255, 255, 255);
if (crosshairfx) { if (crosshairfx) {
if (player1->gunwait) if (player1.gunwait)
glColor3ub(128, 128, 128); glColor3ub(128, 128, 128);
else if (player1->health <= 25) else if (player1.health <= 25)
glColor3ub(255, 0, 0); glColor3ub(255, 0, 0);
else if (player1->health <= 50) else if (player1.health <= 50)
glColor3ub(255, 128, 0); glColor3ub(255, 128, 0);
} }
float chsize = (float)crosshairsize; float chsize = (float)crosshairsize;
@ -418,23 +420,22 @@ gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater)
glPopMatrix(); glPopMatrix();
if (player1->state == CS_ALIVE) { if (player1.state == CS_ALIVE) {
glPushMatrix(); glPushMatrix();
glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1); glOrtho(0, VIRTW / 2, VIRTH / 2, 0, -1, 1);
draw_textf(@"%d", 90, 827, 2, player1->health); draw_textf(@"%d", 90, 827, 2, player1.health);
if (player1->armour) if (player1.armour)
draw_textf(@"%d", 390, 827, 2, player1->armour); draw_textf(@"%d", 390, 827, 2, player1.armour);
draw_textf( draw_textf(@"%d", 690, 827, 2, player1.ammo[player1.gunselect]);
@"%d", 690, 827, 2, player1->ammo[player1->gunselect]);
glPopMatrix(); glPopMatrix();
glPushMatrix(); glPushMatrix();
glOrtho(0, VIRTW, VIRTH, 0, -1, 1); glOrtho(0, VIRTW, VIRTH, 0, -1, 1);
glDisable(GL_BLEND); glDisable(GL_BLEND);
drawicon(128, 128, 20, 1650); drawicon(128, 128, 20, 1650);
if (player1->armour) if (player1.armour)
drawicon( drawicon(
(float)(player1->armourtype * 64), 0, 620, 1650); (float)(player1.armourtype * 64), 0, 620, 1650);
int g = player1->gunselect; int g = player1.gunselect;
int r = 64; int r = 64;
if (g > 2) { if (g > 2) {
g -= 3; g -= 3;

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#ifdef DARWIN #ifdef DARWIN
# define GL_COMBINE_EXT GL_COMBINE_ARB # define GL_COMBINE_EXT GL_COMBINE_ARB
# define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB # define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB
@ -336,14 +338,14 @@ transplayer()
{ {
glLoadIdentity(); glLoadIdentity();
glRotated(player1->roll, 0.0, 0.0, 1.0); glRotated(player1.roll, 0.0, 0.0, 1.0);
glRotated(player1->pitch, -1.0, 0.0, 0.0); glRotated(player1.pitch, -1.0, 0.0, 0.0);
glRotated(player1->yaw, 0.0, 1.0, 0.0); glRotated(player1.yaw, 0.0, 1.0, 0.0);
glTranslated(-player1->o.x, glTranslated(-player1.o.x,
(player1->state == CS_DEAD ? player1->eyeheight - 0.2f : 0) - (player1.state == CS_DEAD ? player1.eyeheight - 0.2f : 0) -
player1->o.z, player1.o.z,
-player1->o.y); -player1.o.y);
} }
VARP(fov, 10, 105, 120); VARP(fov, 10, 105, 120);
@ -361,15 +363,15 @@ OFString *hudgunnames[] = { @"hudguns/fist", @"hudguns/shotg",
void void
drawhudmodel(int start, int end, float speed, int base) drawhudmodel(int start, int end, float speed, int base)
{ {
rendermodel(hudgunnames[player1->gunselect], start, end, 0, 1.0f, rendermodel(hudgunnames[player1.gunselect], start, end, 0, 1.0f,
player1->o.x, player1->o.z, player1->o.y, player1->yaw + 90, player1.o.x, player1.o.z, player1.o.y, player1.yaw + 90,
player1->pitch, false, 1.0f, speed, 0, base); player1.pitch, false, 1.0f, speed, 0, base);
} }
void void
drawhudgun(float fovy, float aspect, int farplane) drawhudgun(float fovy, float aspect, int farplane)
{ {
if (!hudgun /*|| !player1->gunselect*/) if (!hudgun /*|| !player1.gunselect*/)
return; return;
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
@ -380,14 +382,12 @@ drawhudgun(float fovy, float aspect, int farplane)
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
// glClear(GL_DEPTH_BUFFER_BIT); // glClear(GL_DEPTH_BUFFER_BIT);
int rtime = reloadtime(player1->gunselect); int rtime = reloadtime(player1.gunselect);
if (player1->lastaction && if (player1.lastaction && player1.lastattackgun == player1.gunselect &&
player1->lastattackgun == player1->gunselect && lastmillis - player1.lastaction < rtime) {
lastmillis - player1->lastaction < rtime) { drawhudmodel(7, 18, rtime / 18.0f, player1.lastaction);
drawhudmodel(7, 18, rtime / 18.0f, player1->lastaction); } else
} else {
drawhudmodel(6, 1, 100, 0); drawhudmodel(6, 1, 100, 0);
}
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
@ -403,7 +403,7 @@ gl_drawframe(int w, int h, float curfps)
float hf = hdr.waterlevel - 0.3f; float hf = hdr.waterlevel - 0.3f;
float fovy = (float)fov * h / w; float fovy = (float)fov * h / w;
float aspect = w / (float)h; 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_START, (fog + 64) / 8);
glFogi(GL_FOG_END, fog); glFogi(GL_FOG_END, fog);
@ -420,7 +420,7 @@ gl_drawframe(int w, int h, float curfps)
glFogi(GL_FOG_END, (fog + 96) / 8); 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); GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
@ -441,8 +441,8 @@ gl_drawframe(int w, int h, float curfps)
curvert = 0; curvert = 0;
strips.setsize(0); strips.setsize(0);
render_world(player1->o.x, player1->o.y, player1->o.z, render_world(player1.o.x, player1.o.y, player1.o.z, (int)player1.yaw,
(int)player1->yaw, (int)player1->pitch, (float)fov, w, h); (int)player1.pitch, (float)fov, w, h);
finishstrips(); finishstrips();
setupworld(); setupworld();
@ -450,8 +450,8 @@ gl_drawframe(int w, int h, float curfps)
renderstripssky(); renderstripssky();
glLoadIdentity(); glLoadIdentity();
glRotated(player1->pitch, -1.0, 0.0, 0.0); glRotated(player1.pitch, -1.0, 0.0, 0.0);
glRotated(player1->yaw, 0.0, 1.0, 0.0); glRotated(player1.yaw, 0.0, 1.0, 0.0);
glRotated(90.0, 1.0, 0.0, 0.0); glRotated(90.0, 1.0, 0.0, 0.0);
glColor3f(1.0f, 1.0f, 1.0f); glColor3f(1.0f, 1.0f, 1.0f);
glDisable(GL_FOG); glDisable(GL_FOG);

View file

@ -2,6 +2,7 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#import "MD2.h" #import "MD2.h"
#import "MapModelInfo.h" #import "MapModelInfo.h"
@ -100,7 +101,7 @@ rendermodel(OFString *mdl, int frame, int range, int tex, float rad, float x,
{ {
MD2 *m = loadmodel(mdl); 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; return;
delayedload(m); delayedload(m);

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
const int MAXPARTICLES = 10500; const int MAXPARTICLES = 10500;
const int NUMPARTCUTOFF = 20; const int NUMPARTCUTOFF = 20;
struct particle { struct particle {
@ -56,7 +58,7 @@ render_particles(int time)
{ {
if (demoplayback && demotracking) { if (demoplayback && demotracking) {
OFVector3D nom = OFMakeVector3D(0, 0, 0); OFVector3D nom = OFMakeVector3D(0, 0, 0);
newparticle(player1->o, nom, 100000000, 8); newparticle(player1.o, nom, 100000000, 8);
} }
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);

View file

@ -3,17 +3,19 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#ifdef OF_BIG_ENDIAN #ifdef OF_BIG_ENDIAN
static const int islittleendian = 0; static const int islittleendian = 0;
#else #else
static const int islittleendian = 1; static const int islittleendian = 1;
#endif #endif
gzFile f = NULL; static gzFile f = NULL;
bool demorecording = false; bool demorecording = false;
bool demoplayback = false; bool demoplayback = false;
bool demoloading = false; bool demoloading = false;
dvector playerhistory; static OFMutableArray<DynamicEntity *> *playerhistory;
int democlientnum = 0; int democlientnum = 0;
void startdemo(); void startdemo();
@ -76,8 +78,7 @@ stop()
demorecording = false; demorecording = false;
demoplayback = false; demoplayback = false;
demoloading = false; demoloading = false;
loopv(playerhistory) zapdynent(playerhistory[i]); [playerhistory removeAllObjects];
playerhistory.setsize(0);
} }
void void
@ -102,20 +103,27 @@ savestate(OFIRI *IRI)
gzwrite(f, (void *)"CUBESAVE", 8); gzwrite(f, (void *)"CUBESAVE", 8);
gzputc(f, islittleendian); gzputc(f, islittleendian);
gzputi(SAVEGAMEVERSION); gzputi(SAVEGAMEVERSION);
gzputi(sizeof(dynent)); OFData *data = [player1 dataBySerializing];
gzwrite(f, getclientmap().UTF8String, _MAXDEFSTR); gzputi(data.count);
char map[260] = { 0 };
memcpy(map, getclientmap().UTF8String,
min(getclientmap().UTF8StringLength, 259));
gzwrite(f, map, _MAXDEFSTR);
gzputi(gamemode); gzputi(gamemode);
gzputi(ents.length()); gzputi(ents.length());
loopv(ents) gzputc(f, ents[i].spawned); loopv(ents) gzputc(f, ents[i].spawned);
gzwrite(f, player1, sizeof(dynent)); gzwrite(f, data.items, data.count);
dvector &monsters = getmonsters(); OFArray<DynamicEntity *> *monsters = getmonsters();
gzputi(monsters.length()); gzputi(monsters.count);
loopv(monsters) gzwrite(f, monsters[i], sizeof(dynent)); for (DynamicEntity *monster in monsters) {
gzputi(players.length()); data = [monster dataBySerializing];
loopv(players) gzwrite(f, data.items, data.count);
{ }
gzput(players[i] == NULL); gzputi(players.count);
gzwrite(f, players[i], sizeof(dynent)); for (id player in players) {
gzput(player == [OFNull null]);
data = [player dataBySerializing];
gzwrite(f, data.items, data.count);
} }
} }
} }
@ -163,7 +171,8 @@ loadstate(OFIRI *IRI)
goto out; // not supporting save->load accross goto out; // not supporting save->load accross
// incompatible architectures simpifies things // incompatible architectures simpifies things
// a LOT // a LOT
if (gzgeti() != SAVEGAMEVERSION || gzgeti() != sizeof(dynent)) if (gzgeti() != SAVEGAMEVERSION ||
gzgeti() != DynamicEntity.serializedSize)
goto out; goto out;
string mapname; string mapname;
gzread(f, mapname, _MAXDEFSTR); gzread(f, mapname, _MAXDEFSTR);
@ -218,31 +227,37 @@ loadgamerest()
} }
restoreserverstate(ents); restoreserverstate(ents);
gzread(f, player1, sizeof(dynent)); OFMutableData *data =
player1->lastaction = lastmillis; [OFMutableData dataWithCapacity:DynamicEntity.serializedSize];
[data increaseCountBy:DynamicEntity.serializedSize];
gzread(f, data.mutableItems, data.count);
[player1 setFromSerializedData:data];
player1.lastaction = lastmillis;
int nmonsters = gzgeti(); int nmonsters = gzgeti();
dvector &monsters = getmonsters(); OFArray<DynamicEntity *> *monsters = getmonsters();
if (nmonsters != monsters.length()) if (nmonsters != monsters.count)
return loadgameout(); return loadgameout();
loopv(monsters)
{ for (DynamicEntity *monster in monsters) {
gzread(f, monsters[i], sizeof(dynent)); gzread(f, data.mutableItems, data.count);
monsters[i]->enemy = [monster setFromSerializedData:data];
player1; // lazy, could save id of enemy instead // lazy, could save id of enemy instead
monsters[i]->lastaction = monsters[i]->trigger = lastmillis + monster.enemy = player1;
500; // also lazy, but no real noticable effect on game // also lazy, but no real noticable effect on game
if (monsters[i]->state == CS_DEAD) monster.lastaction = monster.trigger = lastmillis + 500;
monsters[i]->lastaction = 0; if (monster.state == CS_DEAD)
monster.lastaction = 0;
} }
restoremonsterstate(); restoremonsterstate();
int nplayers = gzgeti(); int nplayers = gzgeti();
loopi(nplayers) if (!gzget()) loopi(nplayers) if (!gzget())
{ {
dynent *d = getclient(i); DynamicEntity *d = getclient(i);
assert(d); assert(d);
gzread(f, d, sizeof(dynent)); gzread(f, data.mutableItems, data.count);
[d setFromSerializedData:data];
} }
conoutf(@"savegame restored"); conoutf(@"savegame restored");
@ -287,11 +302,12 @@ record(OFString *name)
COMMAND(record, ARG_1STR) COMMAND(record, ARG_1STR)
void void
demodamage(int damage, OFVector3D &o) demodamage(int damage, const OFVector3D &o)
{ {
ddamage = damage; ddamage = damage;
dorig = o; dorig = o;
} }
void void
demoblend(int damage) demoblend(int damage)
{ {
@ -308,15 +324,15 @@ incomingdemodata(uchar *buf, int len, bool extras)
gzwrite(f, buf, len); gzwrite(f, buf, len);
gzput(extras); gzput(extras);
if (extras) { if (extras) {
gzput(player1->gunselect); gzput(player1.gunselect);
gzput(player1->lastattackgun); gzput(player1.lastattackgun);
gzputi(player1->lastaction - starttime); gzputi(player1.lastaction - starttime);
gzputi(player1->gunwait); gzputi(player1.gunwait);
gzputi(player1->health); gzputi(player1.health);
gzputi(player1->armour); gzputi(player1.armour);
gzput(player1->armourtype); gzput(player1.armourtype);
loopi(NUMGUNS) gzput(player1->ammo[i]); loopi(NUMGUNS) gzput(player1.ammo[i]);
gzput(player1->state); gzput(player1.state);
gzputi(bdamage); gzputi(bdamage);
bdamage = 0; bdamage = 0;
gzputi(ddamage); gzputi(ddamage);
@ -348,7 +364,7 @@ stopreset()
{ {
conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime); conoutf(@"demo stopped (%d msec elapsed)", lastmillis - starttime);
stop(); stop();
loopv(players) zapdynent(players[i]); [players removeAllObjects];
disconnect(0, 0); disconnect(0, 0);
} }
@ -376,47 +392,44 @@ startdemo()
demoplayback = true; demoplayback = true;
starttime = lastmillis; starttime = lastmillis;
conoutf(@"now playing demo"); conoutf(@"now playing demo");
dynent *d = getclient(democlientnum); setclient(democlientnum, [player1 copy]);
assert(d);
*d = *player1;
readdemotime(); readdemotime();
} }
VAR(demodelaymsec, 0, 120, 500); VAR(demodelaymsec, 0, 120, 500);
// spline interpolation // spline interpolation
void #define catmulrom(z, a, b, c, s, dest) \
catmulrom(OFVector3D &z, OFVector3D &a, OFVector3D &b, OFVector3D &c, float s, { \
OFVector3D &dest) OFVector3D t1 = b, t2 = c; \
{ \
OFVector3D t1 = b, t2 = c; vsub(t1, z); \
vmul(t1, 0.5f); \
vsub(t1, z); vsub(t2, a); \
vmul(t1, 0.5f) vsub(t2, a); vmul(t2, 0.5f); \
vmul(t2, 0.5f); \
float s2 = s * s; \
float s2 = s * s; float s3 = s * s2; \
float s3 = s * s2; \
dest = a; \
dest = a; OFVector3D t = b; \
OFVector3D t = b; \
vmul(dest, 2 * s3 - 3 * s2 + 1); \
vmul(dest, 2 * s3 - 3 * s2 + 1); vmul(t, -2 * s3 + 3 * s2); \
vmul(t, -2 * s3 + 3 * s2); vadd(dest, t); \
vadd(dest, t); vmul(t1, s3 - 2 * s2 + s); \
vmul(t1, s3 - 2 * s2 + s); vadd(dest, t1); \
vadd(dest, t1); vmul(t2, s3 - s2); \
vmul(t2, s3 - s2); vadd(dest, t2); \
vadd(dest, t2);
} }
void void
fixwrap(dynent *a, dynent *b) fixwrap(DynamicEntity *a, DynamicEntity *b)
{ {
while (b->yaw - a->yaw > 180) while (b.yaw - a.yaw > 180)
a->yaw += 360; a.yaw += 360;
while (b->yaw - a->yaw < -180) while (b.yaw - a.yaw < -180)
a->yaw -= 360; a.yaw -= 360;
} }
void void
@ -434,23 +447,23 @@ demoplaybackstep()
gzread(f, buf, len); gzread(f, buf, len);
localservertoclient(buf, len); // update game state localservertoclient(buf, len); // update game state
dynent *target = players[democlientnum]; DynamicEntity *target = players[democlientnum];
assert(target); assert(target);
int extras; int extras;
if (extras = gzget()) // read additional client side state not // read additional client side state not present in normal
// present in normal network stream // network stream
{ if (extras = gzget()) {
target->gunselect = gzget(); target.gunselect = gzget();
target->lastattackgun = gzget(); target.lastattackgun = gzget();
target->lastaction = scaletime(gzgeti()); target.lastaction = scaletime(gzgeti());
target->gunwait = gzgeti(); target.gunwait = gzgeti();
target->health = gzgeti(); target.health = gzgeti();
target->armour = gzgeti(); target.armour = gzgeti();
target->armourtype = gzget(); target.armourtype = gzget();
loopi(NUMGUNS) target->ammo[i] = gzget(); loopi(NUMGUNS) target.ammo[i] = gzget();
target->state = gzget(); target.state = gzget();
target->lastmove = playbacktime; target.lastmove = playbacktime;
if (bdamage = gzgeti()) if (bdamage = gzgeti())
damageblend(bdamage); damageblend(bdamage);
if (ddamage = gzgeti()) { if (ddamage = gzgeti()) {
@ -462,66 +475,87 @@ demoplaybackstep()
// insert latest copy of player into history // insert latest copy of player into history
if (extras && if (extras &&
(playerhistory.empty() || (playerhistory.count == 0 ||
playerhistory.last()->lastupdate != playbacktime)) { playerhistory.lastObject.lastupdate != playbacktime)) {
dynent *d = newdynent(); DynamicEntity *d = [target copy];
*d = *target; d.lastupdate = playbacktime;
d->lastupdate = playbacktime; [playerhistory addObject:d];
playerhistory.add(d); if (playerhistory.count > 20)
if (playerhistory.length() > 20) { [playerhistory removeObjectAtIndex:0];
zapdynent(playerhistory[0]);
playerhistory.remove(0);
}
} }
readdemotime(); readdemotime();
} }
if (demoplayback) { if (!demoplayback)
return;
int itime = lastmillis - demodelaymsec; int itime = lastmillis - demodelaymsec;
loopvrev(playerhistory) if (playerhistory[i]->lastupdate < // find 2 positions in history that surround interpolation time point
itime) // find 2 positions in size_t count = playerhistory.count;
// history that surround for (ssize_t i = count - 1; i >= 0; i--) {
// interpolation time point if (playerhistory[i].lastupdate < itime) {
{ DynamicEntity *a = playerhistory[i];
dynent *a = playerhistory[i]; DynamicEntity *b = a;
dynent *b = a;
if (i + 1 < playerhistory.length()) if (i + 1 < playerhistory.count)
b = playerhistory[i + 1]; b = playerhistory[i + 1];
*player1 = *b;
if (a != b) // interpolate pos & angles player1 = b;
{ // interpolate pos & angles
dynent *c = b; if (a != b) {
if (i + 2 < playerhistory.length()) DynamicEntity *c = b;
if (i + 2 < playerhistory.count)
c = playerhistory[i + 2]; c = playerhistory[i + 2];
dynent *z = a; DynamicEntity *z = a;
if (i - 1 >= 0) if (i - 1 >= 0)
z = playerhistory[i - 1]; z = playerhistory[i - 1];
// if(a==z || b==c) printf("* %d\n", // if(a==z || b==c)
// lastmillis); // printf("* %d\n", lastmillis);
float bf = (itime - a->lastupdate) / float bf = (itime - a.lastupdate) /
(float)(b->lastupdate - a->lastupdate); (float)(b.lastupdate - a.lastupdate);
fixwrap(a, player1); fixwrap(a, player1);
fixwrap(c, player1); fixwrap(c, player1);
fixwrap(z, player1); fixwrap(z, player1);
vdist(dist, v, z->o, c->o); vdist(dist, v, z.o, c.o);
if (dist < 16) // if teleport or spawn, dont't // if teleport or spawn, don't interpolate
// interpolate if (dist < 16) {
{ catmulrom(
catmulrom(z->o, a->o, b->o, c->o, bf, z.o, a.o, b.o, c.o, bf, player1.o);
player1->o); OFVector3D vz = OFMakeVector3D(
catmulrom(*(OFVector3D *)&z->yaw, z.yaw, z.pitch, z.roll);
*(OFVector3D *)&a->yaw, OFVector3D va = OFMakeVector3D(
*(OFVector3D *)&b->yaw, a.yaw, a.pitch, a.roll);
*(OFVector3D *)&c->yaw, bf, OFVector3D vb = OFMakeVector3D(
*(OFVector3D *)&player1->yaw); 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(); fixplayer1range();
} }
break; break;
} }
// if(player1->state!=CS_DEAD) showscores(false);
} }
// if(player1->state!=CS_DEAD) showscores(false);
} }
void void

View file

@ -3,6 +3,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
// #ifndef _WIN32 // NOTE: fmod not being supported for the moment as it does // #ifndef _WIN32 // NOTE: fmod not being supported for the moment as it does
// not allow stereo pan/vol updating during playback // not allow stereo pan/vol updating during playback
#define USE_MIXER #define USE_MIXER
@ -173,17 +175,15 @@ updatechanvol(int chan, const OFVector3D *loc)
{ {
int vol = soundvol, pan = 255 / 2; int vol = soundvol, pan = 255 / 2;
if (loc) { if (loc) {
vdist(dist, v, *loc, player1->o); vdist(dist, v, *loc, player1.o);
vol -= (int)(dist * 3 * soundvol / vol -= (int)(dist * 3 * soundvol /
255); // simple mono distance attenuation 255); // simple mono distance attenuation
if (stereo && (v.x != 0 || v.y != 0)) { if (stereo && (v.x != 0 || v.y != 0)) {
float yaw = -atan2(v.x, v.y) - // relative angle of sound along X-Y axis
player1->yaw * float yaw =
(PI / 180.0f); // relative angle of -atan2(v.x, v.y) - player1.yaw * (PI / 180.0f);
// sound along X-Y axis // range is from 0 (left) to 255 (right)
pan = int(255.9f * pan = int(255.9f * (0.5 * sin(yaw) + 0.5f));
(0.5 * sin(yaw) + 0.5f)); // range is from 0 (left)
// to 255 (right)
} }
} }
vol = (vol * MAXVOL) / 255; vol = (vol * MAXVOL) / 255;

View file

@ -2,6 +2,7 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#import "Projectile.h" #import "Projectile.h"
static const int MONSTERDAMAGEFACTOR = 4; static const int MONSTERDAMAGEFACTOR = 4;
@ -30,26 +31,26 @@ selectgun(int a, int b, int c)
if (a < -1 || b < -1 || c < -1 || a >= NUMGUNS || b >= NUMGUNS || if (a < -1 || b < -1 || c < -1 || a >= NUMGUNS || b >= NUMGUNS ||
c >= NUMGUNS) c >= NUMGUNS)
return; return;
int s = player1->gunselect; int s = player1.gunselect;
if (a >= 0 && s != a && player1->ammo[a]) if (a >= 0 && s != a && player1.ammo[a])
s = a; s = a;
else if (b >= 0 && s != b && player1->ammo[b]) else if (b >= 0 && s != b && player1.ammo[b])
s = b; s = b;
else if (c >= 0 && s != c && player1->ammo[c]) else if (c >= 0 && s != c && player1.ammo[c])
s = c; s = c;
else if (s != GUN_RL && player1->ammo[GUN_RL]) else if (s != GUN_RL && player1.ammo[GUN_RL])
s = 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; s = GUN_CG;
else if (s != GUN_SG && player1->ammo[GUN_SG]) else if (s != GUN_SG && player1.ammo[GUN_SG])
s = 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; s = GUN_RIFLE;
else else
s = GUN_FIST; s = GUN_FIST;
if (s != player1->gunselect) if (s != player1.gunselect)
playsoundc(S_WEAPLOAD); playsoundc(S_WEAPLOAD);
player1->gunselect = s; player1.gunselect = s;
// conoutf(@"%@ selected", (int)guns[s].name); // conoutf(@"%@ selected", (int)guns[s].name);
} }
@ -85,9 +86,9 @@ createrays(OFVector3D &from,
// if lineseg hits entity bounding box // if lineseg hits entity bounding box
bool 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; const OFVector3D *p;
vsub(v, from); vsub(v, from);
vsub(w, from); vsub(w, from);
@ -107,9 +108,9 @@ intersect(dynent *d, const OFVector3D &from, const OFVector3D &to)
} }
} }
return (p->x <= d->o.x + d->radius && p->x >= d->o.x - d->radius && 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->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); p->z <= d.o.z + d.aboveeye && p->z >= d.o.z - d.eyeheight);
} }
OFString * OFString *
@ -117,13 +118,13 @@ playerincrosshair()
{ {
if (demoplayback) if (demoplayback)
return NULL; return NULL;
loopv(players)
{ for (id player in players) {
dynent *o = players[i]; if (player == [OFNull null])
if (!o)
continue; continue;
if (intersect(o, player1->o, worldpos))
return @(o->name); if (intersect(player, player1.o, worldpos))
return [player name];
} }
return nil; return nil;
@ -141,7 +142,7 @@ projreset()
void void
newprojectile(OFVector3D &from, OFVector3D &to, float speed, bool local, 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++) { for (size_t i = 0; i < MAXPROJ; i++) {
Projectile *p = projs[i]; Projectile *p = projs[i];
@ -161,29 +162,31 @@ newprojectile(OFVector3D &from, OFVector3D &to, float speed, bool local,
} }
void void
hit(int target, int damage, dynent *d, dynent *at) hit(int target, int damage, DynamicEntity *d, DynamicEntity *at)
{ {
if (d == player1) if (d == player1)
selfdamage(damage, at == player1 ? -1 : -2, at); selfdamage(damage, at == player1 ? -1 : -2, at);
else if (d->monsterstate) else if (d.monsterstate)
monsterpain(d, damage, at); monsterpain(d, damage, at);
else { else {
addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); addmsg(1, 4, SV_DAMAGE, target, damage, d.lifesequence);
playsound(S_PAIN1 + rnd(5), &d->o); OFVector3D loc = d.o;
playsound(S_PAIN1 + rnd(5), &loc);
} }
particle_splash(3, damage, 1000, d->o); particle_splash(3, damage, 1000, d.o);
demodamage(damage, d->o); demodamage(damage, d.o);
} }
const float RL_RADIUS = 5; const float RL_RADIUS = 5;
const float RL_DAMRAD = 7; // hack const float RL_DAMRAD = 7; // hack
static void 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; return;
vdist(dist, temp, v, o->o); vdist(dist, temp, v, o.o);
dist -= 2; // account for eye distance imprecision dist -= 2; // account for eye distance imprecision
if (dist < RL_DAMRAD) { if (dist < RL_DAMRAD) {
if (dist < 0) if (dist < 0)
@ -191,7 +194,7 @@ radialeffect(dynent *o, const OFVector3D &v, int cn, int qdam, dynent *at)
int damage = (int)(qdam * (1 - (dist / RL_DAMRAD))); int damage = (int)(qdam * (1 - (dist / RL_DAMRAD)));
hit(cn, damage, o, at); hit(cn, damage, o, at);
vmul(temp, (RL_DAMRAD - dist) * damage / 800); vmul(temp, (RL_DAMRAD - dist) * damage / 800);
vadd(o->vel, temp); vadd(o.vel, temp);
} }
} }
@ -211,25 +214,32 @@ splash(Projectile *p, const OFVector3D &v, const OFVector3D &vold,
if (!p.local) if (!p.local)
return; return;
radialeffect(player1, v, -1, qdam, p.owner); radialeffect(player1, v, -1, qdam, p.owner);
loopv(players)
{ size_t i = 0;
if (i == notthisplayer) for (id player in players) {
if (i == notthisplayer) {
i++;
continue; continue;
dynent *o = players[i];
if (!o)
continue;
radialeffect(o, v, i, qdam, p.owner);
} }
dvector &mv = getmonsters();
loopv(mv) if (i != notthismonster) if (player != [OFNull null])
radialeffect(mv[i], v, i, qdam, p.owner); 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 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; return;
if (intersect(o, p.o, v)) { if (intersect(o, p.o, v)) {
splash(p, v, p.o, i, im, qdam); splash(p, v, p.o, i, im, qdam);
@ -246,8 +256,8 @@ moveprojectiles(float time)
if (!p.inuse) if (!p.inuse)
continue; continue;
int qdam = guns[p.gun].damage * (p.owner->quadmillis ? 4 : 1); int qdam = guns[p.gun].damage * (p.owner.quadmillis ? 4 : 1);
if (p.owner->monsterstate) if (p.owner.monsterstate)
qdam /= MONSTERDAMAGEFACTOR; qdam /= MONSTERDAMAGEFACTOR;
vdist(dist, v, p.o, p.to); vdist(dist, v, p.o, p.to);
float dtime = dist * 1000 / p.speed; float dtime = dist * 1000 / p.speed;
@ -256,19 +266,17 @@ moveprojectiles(float time)
vmul(v, time / dtime); vmul(v, time / dtime);
vadd(v, p.o) if (p.local) vadd(v, p.o) if (p.local)
{ {
loopv(players) for (id player in players)
{ if (player != [OFNull null])
dynent *o = players[i]; projdamage(player, p, v, i, -1, qdam);
if (!o)
continue;
projdamage(o, p, v, i, -1, qdam);
}
if (p.owner != player1) if (p.owner != player1)
projdamage(player1, p, v, -1, -1, qdam); projdamage(player1, p, v, -1, -1, qdam);
dvector &mv = getmonsters();
loopv(mv) if (!vreject(mv[i]->o, v, 10.0f) && for (DynamicEntity *monster in getmonsters())
mv[i] != p.owner) if (!vreject(monster.o, v, 10.0f) &&
projdamage(mv[i], p, v, -1, i, qdam); monster != p.owner)
projdamage(monster, p, v, -1, i, qdam);
} }
if (p.inuse) { if (p.inuse) {
if (time == dtime) if (time == dtime)
@ -288,11 +296,12 @@ moveprojectiles(float time)
} }
} }
// create visual effect from a shot
void void
shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d, shootv(int gun, OFVector3D &from, OFVector3D &to, DynamicEntity *d, bool local)
bool local) // create visual effect from a shot
{ {
playsound(guns[gun].sound, d == player1 ? NULL : &d->o); OFVector3D loc = d.o;
playsound(guns[gun].sound, d == player1 ? NULL : &loc);
int pspeed = 25; int pspeed = 25;
switch (gun) { switch (gun) {
case GUN_FIST: case GUN_FIST:
@ -313,7 +322,7 @@ shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d,
case GUN_ICEBALL: case GUN_ICEBALL:
case GUN_SLIMEBALL: case GUN_SLIMEBALL:
pspeed = guns[gun].projspeed; pspeed = guns[gun].projspeed;
if (d->monsterstate) if (d.monsterstate)
pspeed /= 2; pspeed /= 2;
newprojectile(from, to, (float)pspeed, local, d, gun); newprojectile(from, to, (float)pspeed, local, d, gun);
break; break;
@ -326,26 +335,27 @@ shootv(int gun, OFVector3D &from, OFVector3D &to, dynent *d,
} }
void void
hitpush(int target, int damage, dynent *d, dynent *at, OFVector3D &from, hitpush(int target, int damage, DynamicEntity *d, DynamicEntity *at,
OFVector3D &to) OFVector3D &from, OFVector3D &to)
{ {
hit(target, damage, d, at); hit(target, damage, d, at);
vdist(dist, v, from, to); vdist(dist, v, from, to);
vmul(v, damage / dist / 50); vmul(v, damage / dist / 50);
vadd(d->vel, v); vadd(d.vel, v);
} }
void 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; return;
int qdam = guns[d->gunselect].damage; int qdam = guns[d.gunselect].damage;
if (d->quadmillis) if (d.quadmillis)
qdam *= 4; qdam *= 4;
if (d->monsterstate) if (d.monsterstate)
qdam /= MONSTERDAMAGEFACTOR; qdam /= MONSTERDAMAGEFACTOR;
if (d->gunselect == GUN_SG) { if (d.gunselect == GUN_SG) {
int damage = 0; int damage = 0;
loop(r, SGRAYS) if (intersect(o, from, sg[r])) damage += qdam; loop(r, SGRAYS) if (intersect(o, from, sg[r])) damage += qdam;
if (damage) if (damage)
@ -355,67 +365,67 @@ raydamage(dynent *o, OFVector3D &from, OFVector3D &to, dynent *d, int i)
} }
void void
shoot(dynent *d, OFVector3D &targ) shoot(DynamicEntity *d, const OFVector3D &targ)
{ {
int attacktime = lastmillis - d->lastaction; int attacktime = lastmillis - d.lastaction;
if (attacktime < d->gunwait) if (attacktime < d.gunwait)
return; return;
d->gunwait = 0; d.gunwait = 0;
if (!d->attacking) if (!d.attacking)
return; return;
d->lastaction = lastmillis; d.lastaction = lastmillis;
d->lastattackgun = d->gunselect; d.lastattackgun = d.gunselect;
if (!d->ammo[d->gunselect]) { if (!d.ammo[d.gunselect]) {
playsoundc(S_NOAMMO); playsoundc(S_NOAMMO);
d->gunwait = 250; d.gunwait = 250;
d->lastattackgun = -1; d.lastattackgun = -1;
return; return;
} }
if (d->gunselect) if (d.gunselect)
d->ammo[d->gunselect]--; d.ammo[d.gunselect]--;
OFVector3D from = d->o; OFVector3D from = d.o;
OFVector3D to = targ; OFVector3D to = targ;
from.z -= 0.2f; // below eye from.z -= 0.2f; // below eye
vdist(dist, unitv, from, to); vdist(dist, unitv, from, to);
vdiv(unitv, dist); vdiv(unitv, dist);
OFVector3D kickback = unitv; OFVector3D kickback = unitv;
vmul(kickback, guns[d->gunselect].kickamount * -0.01f); vmul(kickback, guns[d.gunselect].kickamount * -0.01f);
vadd(d->vel, kickback); vadd(d.vel, kickback);
if (d->pitch < 80.0f) if (d.pitch < 80.0f)
d->pitch += guns[d->gunselect].kickamount * 0.05f; 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 vmul(unitv, 3); // punch range
to = from; to = from;
vadd(to, unitv); vadd(to, unitv);
} }
if (d->gunselect == GUN_SG) if (d.gunselect == GUN_SG)
createrays(from, to); createrays(from, to);
if (d->quadmillis && attacktime > 200) if (d.quadmillis && attacktime > 200)
playsoundc(S_ITEMPUP); playsoundc(S_ITEMPUP);
shootv(d->gunselect, from, to, d, true); shootv(d.gunselect, from, to, d, true);
if (!d->monsterstate) if (!d.monsterstate)
addmsg(1, 8, SV_SHOT, d->gunselect, (int)(from.x * DMF), addmsg(1, 8, SV_SHOT, d.gunselect, (int)(from.x * DMF),
(int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF), (int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF),
(int)(to.y * DMF), (int)(to.z * 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; return;
loopv(players) size_t i = 0;
{ for (id player in players) {
dynent *o = players[i]; if (player != [OFNull null])
if (!o) raydamage(player, from, to, d, i);
continue; i++;
raydamage(o, from, to, d, i);
} }
dvector &v = getmonsters(); for (DynamicEntity *monster in getmonsters())
loopv(v) if (v[i] != d) raydamage(v[i], from, to, d, -2); if (monster != d)
raydamage(monster, from, to, d, -2);
if (d->monsterstate) if (d.monsterstate)
raydamage(player1, from, to, d, -1); raydamage(player1, from, to, d, -1);
} }

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
extern OFString *entnames[]; // lookup from map entities above to strings extern OFString *entnames[]; // lookup from map entities above to strings
sqr *world = NULL; sqr *world = NULL;
@ -9,9 +11,10 @@ int sfactor, ssize, cubicsize, mipsize;
header hdr; header hdr;
// set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes
// according to type
void void
settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then settag(int tag, int type)
// reset ALL tagged cubes according to type
{ {
int maxx = 0, maxy = 0, minx = ssize, miny = ssize; int maxx = 0, maxy = 0, minx = ssize, miny = ssize;
loop(x, ssize) loop(y, ssize) loop(x, ssize) loop(y, ssize)
@ -263,7 +266,7 @@ closestent() // used for delent and edit mode ent display
if (e.type == NOTUSED) if (e.type == NOTUSED)
continue; continue;
OFVector3D v = OFMakeVector3D(e.x, e.y, e.z); OFVector3D v = OFMakeVector3D(e.x, e.y, e.z);
vdist(dist, t, player1->o, v); vdist(dist, t, player1.o, v);
if (dist < bdist) { if (dist < bdist) {
best = i; best = i;
bdist = dist; bdist = dist;
@ -345,7 +348,7 @@ newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4)
case TELEDEST: case TELEDEST:
e.attr2 = (uchar)e.attr1; e.attr2 = (uchar)e.attr1;
case PLAYERSTART: case PLAYERSTART:
e.attr1 = (int)player1->yaw; e.attr1 = (int)player1.yaw;
break; break;
} }
addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1, addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1,

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
extern bool hasoverbright; extern bool hasoverbright;
VAR(lightscale, 1, 4, 100); VAR(lightscale, 1, 4, 100);
@ -201,11 +203,11 @@ cleardlights()
void void
dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, int strength, dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, int strength,
dynent *owner) DynamicEntity *owner)
{ {
if (!reach) if (!reach)
reach = dynlight; reach = dynlight;
if (owner->monsterstate) if (owner.monsterstate)
reach = reach / 2; reach = reach / 2;
if (!reach) if (!reach)
return; return;

View file

@ -2,6 +2,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
#define NUMRAYS 512 #define NUMRAYS 512
float rdist[NUMRAYS]; float rdist[NUMRAYS];
@ -26,10 +28,10 @@ computeraytable(float vx, float vy)
odist = getvar(@"fog") * 1.5f; 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 af = getvar(@"fov") / 2 + apitch / 1.5f + 3;
float byaw = (player1->yaw - 90 + af) / 360 * PI2; float byaw = (player1.yaw - 90 + af) / 360 * PI2;
float syaw = (player1->yaw - 90 - af) / 360 * PI2; float syaw = (player1.yaw - 90 - af) / 360 * PI2;
loopi(NUMRAYS) loopi(NUMRAYS)
{ {

View file

@ -4,6 +4,8 @@
#include "cube.h" #include "cube.h"
#import "DynamicEntity.h"
void void
render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1,
sqr *d2, bool topleft) sqr *d2, bool topleft)
@ -135,7 +137,7 @@ render_seg_new(
// first collect occlusion information for this block // first collect occlusion information for this block
for (int oy = y; oy < ys; oy++) { for (int oy = y; oy < ys; oy++) {
SWS(w, ox, oy, sz)->occluded = 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); (float)(ox << mip), (float)(oy << mip), fsize);
} }
} }
@ -144,8 +146,8 @@ render_seg_new(
int pvy = (int)vy >> mip; int pvy = (int)vy >> mip;
if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) { if (pvx >= 0 && pvy >= 0 && pvx < sz && pvy < sz) {
// SWS(w,vxx,vyy,sz)->occluded = 0; // SWS(w,vxx,vyy,sz)->occluded = 0;
SWS(w, pvx, pvy, sz)->occluded = // player cell never occluded
0; // player cell never occluded SWS(w, pvx, pvy, sz)->occluded = 0;
} }
#define df(x) s->floor - (x->vdelta / 4.0f) #define df(x) s->floor - (x->vdelta / 4.0f)