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

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>
typedef struct dynent dynent;
@class DynamicEntity;
@interface Projectile: OFObject
@property (nonatomic) OFVector3D o, to;
@property (nonatomic) float speed;
@property (nonatomic) dynent *owner;
@property (nonatomic) DynamicEntity *owner;
@property (nonatomic) int gun;
@property (nonatomic) bool inuse, local;
@end

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,8 @@
#include "tools.h"
@class DynamicEntity;
@interface Cube: OFObject <OFApplicationDelegate>
@property (class, readonly, nonatomic) Cube *sharedInstance;
@property (readonly, nonatomic) SDL_Window *window;
@ -128,41 +130,8 @@ enum {
NUMGUNS
};
struct dynent // players & monsters
{
OFVector3D o, vel; // origin, velocity
float yaw, pitch, roll; // used as OFVector3D in one place
float maxspeed; // cubes per second, 24 for player
bool outsidemap; // from his eyes
bool inwater;
bool onfloor, jumpnext;
int move, strafe;
bool k_left, k_right, k_up, k_down; // see input code
int timeinair; // used for fake gravity
float radius, eyeheight, aboveeye; // bounding box size
int lastupdate, plag, ping;
int lifesequence; // sequence id for each respawn, used in damage test
int state; // one of CS_* below
int frags;
int health, armour, armourtype, quadmillis;
int gunselect, gunwait;
int lastaction, lastattackgun, lastmove;
bool attacking;
int ammo[NUMGUNS];
int monsterstate; // one of M_* below, M_NONE means human
int mtype; // see monster.cpp
dynent *enemy; // monster wants to kill this entity
float targetyaw; // monster wants to look in this direction
bool blocked, moving; // used by physics to signal ai
int trigger; // millis at which transition to another monsterstate takes
// place
OFVector3D attacktarget; // delayed attacks
int anger; // how many times already hit by fellow monster
string name, team;
};
#define SAVEGAMEVERSION \
4 // bump if dynent/netprotocol changes or any other savegame/demo data
// bump if dynent/netprotocol changes or any other savegame/demo data
#define SAVEGAMEVERSION 4
enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off
enum {
@ -281,8 +250,6 @@ struct vertex {
uchar r, g, b, a;
};
typedef vector<dynent *> dvector;
// globals ooh naughty
extern sqr *world,
@ -290,9 +257,10 @@ extern sqr *world,
extern header hdr; // current map header
extern int sfactor, ssize; // ssize = 2^sfactor
extern int cubicsize, mipsize; // cubicsize = ssize^2
extern dynent
*player1; // special client ent that receives input and acts as camera
extern dvector players; // all the other clients (in multiplayer)
// special client ent that receives input and acts as camera
extern DynamicEntity *player1;
// all the other clients (in multiplayer)
extern OFMutableArray *players;
extern bool editmode;
extern vector<entity> ents; // map entities
extern OFVector3D worldpos; // current target of the crosshair in the world
@ -316,30 +284,32 @@ extern bool demoplayback;
// simplistic vector ops
#define dotprod(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
#define vmul(u, f) \
{ \
(u).x *= (f); \
(u).y *= (f); \
(u).z *= (f); \
#define vmul(u, f) \
{ \
OFVector3D tmp_ = u; \
float tmp2_ = f; \
u = OFMakeVector3D( \
tmp_.x * tmp2_, tmp_.y * tmp2_, tmp_.z * tmp2_); \
}
#define vdiv(u, f) \
{ \
(u).x /= (f); \
(u).y /= (f); \
(u).z /= (f); \
#define vdiv(u, f) \
{ \
OFVector3D tmp_ = u; \
float tmp2_ = f; \
u = OFMakeVector3D( \
tmp_.x / tmp2_, tmp_.y / tmp2_, tmp_.z / tmp2_); \
}
#define vadd(u, v) \
{ \
OFVector3D tmp_ = u; \
u = OFMakeVector3D( \
tmp_.x + (v).x, tmp_.y + (v).y, tmp_.z + (v).z); \
}
#define vsub(u, v) \
{ \
OFVector3D tmp_ = u; \
u = OFMakeVector3D( \
tmp_.x - (v).x, tmp_.y - (v).y, tmp_.z - (v).z); \
}
#define vadd(u, v) \
{ \
(u).x += (v).x; \
(u).y += (v).y; \
(u).z += (v).z; \
};
#define vsub(u, v) \
{ \
(u).x -= (v).x; \
(u).y -= (v).y; \
(u).z -= (v).z; \
};
#define vdist(d, v, e, s) \
OFVector3D v = s; \
vsub(v, e); \
@ -370,7 +340,7 @@ extern bool demoplayback;
#define m_sp (gamemode < 0)
#define m_dmsp (gamemode == -1)
#define m_classicsp (gamemode == -2)
#define isteam(a, b) (m_teammode && strcmp(a, b) == 0)
#define isteam(a, b) (m_teammode && [a isEqual:b])
enum // function signatures for script functions, see command.cpp
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "MD2.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);
if (isoccluded(player1->o.x, player1->o.y, x - rad, z - rad, rad * 2))
if (isoccluded(player1.o.x, player1.o.y, x - rad, z - rad, rad * 2))
return;
delayedload(m);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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