Convert entity to a class

FossilOrigin-Name: 4b002822f954056510cbb1f85d7687784e26782f63c5995f01ac6488d0632d80
This commit is contained in:
Jonathan Schleifer 2025-03-20 16:04:35 +00:00
parent 34b31eb77f
commit d42f82f1ec
19 changed files with 222 additions and 122 deletions

View file

@ -94,6 +94,7 @@ VARP(minmillis, 0, 5, 1000);
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | par) < 0)
fatal(@"Unable to initialize SDL");
initEntities();
initPlayers();
log(@"net");

5
src/Entity.h Normal file
View file

@ -0,0 +1,5 @@
#import "PersistentEntity.h"
@interface Entity: PersistentEntity
@property (nonatomic) bool spawned; // the only dynamic state of a map entity
@end

4
src/Entity.m Normal file
View file

@ -0,0 +1,4 @@
#import "Entity.h"
@implementation Entity
@end

11
src/PersistentEntity.h Normal file
View file

@ -0,0 +1,11 @@
#import <ObjFW/ObjFW.h>
// map entity
@interface PersistentEntity: OFObject
@property (nonatomic) short x, y, z; // cube aligned position
@property (nonatomic) short attr1;
@property (nonatomic) unsigned char type; // type is one of the above
@property (nonatomic) unsigned char attr2, attr3, attr4;
+ (instancetype)entity;
@end

8
src/PersistentEntity.m Normal file
View file

@ -0,0 +1,8 @@
#import "PersistentEntity.h"
@implementation PersistentEntity
+ (instancetype)entity
{
return [[self alloc] init];
}
@end

View file

@ -3,6 +3,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "OFString+Cube.h"
int nextmode = 0; // nextmode becomes gamemode after next map load

View file

@ -3,6 +3,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
extern int clientnum;
extern bool c2sinit, senditemstoserver;
@ -280,7 +281,7 @@ localservertoclient(uchar *buf, int len)
case SV_ITEMSPAWN: {
uint i = getint(p);
setspawn(i, true);
if (i >= (uint)ents.length())
if (i >= (uint)ents.count)
break;
OFVector3D v =
OFMakeVector3D(ents[i].x, ents[i].y, ents[i].z);
@ -328,8 +329,13 @@ localservertoclient(uchar *buf, int len)
case SV_EDITENT: // coop edit of ent
{
uint i = getint(p);
while ((uint)ents.length() <= i)
ents.add().type = NOTUSED;
while ((uint)ents.count <= i) {
Entity *e = [Entity entity];
e.type = NOTUSED;
[ents addObject:e];
}
int to = ents[i].type;
ents[i].type = getint(p);
ents[i].x = getint(p);

View file

@ -10,6 +10,7 @@
#define _MAXDEFSTR 260
@class Entity;
@class DynamicEntity;
@interface Cube: OFObject <OFApplicationDelegate>
@ -76,18 +77,6 @@ enum {
MAXENTTYPES
};
// map entity
struct persistent_entity {
short x, y, z; // cube aligned position
short attr1;
uchar type; // type is one of the above
uchar attr2, attr3, attr4;
};
struct entity: public persistent_entity {
bool spawned; // the only dynamic state of a map entity
};
#define MAPVERSION 5 // bump if map format changes, see worldio.cpp
// map file format header
@ -264,7 +253,7 @@ extern DynamicEntity *player1;
// all the other clients (in multiplayer)
extern OFMutableArray *players;
extern bool editmode;
extern vector<entity> ents; // map entities
extern OFMutableArray<Entity *> *ents; // map entities
extern OFVector3D worldpos; // current target of the crosshair in the world
extern int lastmillis; // last time
extern int curtime; // current frame time

View file

@ -3,9 +3,10 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "MapModelInfo.h"
vector<entity> ents;
OFMutableArray<Entity *> *ents;
static OFString *entmdlnames[] = {
@"shells",
@ -23,7 +24,13 @@ static OFString *entmdlnames[] = {
int triggertime = 0;
void
renderent(entity &e, OFString *mdlname, float z, float yaw, int frame = 0,
initEntities()
{
ents = [[OFMutableArray alloc] init];
}
void
renderent(Entity *e, OFString *mdlname, float z, float yaw, int frame = 0,
int numf = 1, int basetime = 0, float speed = 10.0f)
{
rendermodel(mdlname, frame, numf, 0, 1.1f,
@ -36,9 +43,8 @@ renderentities()
{
if (lastmillis > triggertime + 1000)
triggertime = 0;
loopv(ents)
{
entity &e = ents[i];
for (Entity *e in ents) {
if (e.type == MAPMODEL) {
MapModelInfo *mmi = getmminfo(e.attr2);
if (mmi == nil)
@ -299,21 +305,24 @@ checkitems()
{
if (editmode)
return;
loopv(ents)
{
entity &e = ents[i];
[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
if (e.type == NOTUSED)
continue;
if (!ents[i].spawned && e.type != TELEPORT && e.type != JUMPPAD)
continue;
return;
if (!e.spawned && e.type != TELEPORT && e.type != JUMPPAD)
return;
if (OUTBORD(e.x, e.y))
continue;
return;
OFVector3D v = OFMakeVector3D(
e.x, e.y, (float)S(e.x, e.y)->floor + player1.eyeheight);
vdist(dist, t, player1.o, v);
if (dist < (e.type == TELEPORT ? 4 : 2.5))
pickup(i, player1);
}
}];
}
void
@ -329,22 +338,24 @@ checkquad(int time)
void
putitems(uchar *&p) // puts items in network stream and also spawns them locally
{
loopv(ents) if ((ents[i].type >= I_SHELLS && ents[i].type <= I_QUAD) ||
ents[i].type == CARROT)
{
[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
if ((e.type >= I_SHELLS && e.type <= I_QUAD) ||
e.type == CARROT) {
putint(p, i);
ents[i].spawned = true;
e.spawned = true;
}
}];
}
void
resetspawns()
{
loopv(ents) ents[i].spawned = false;
for (Entity *e in ents)
e.spawned = false;
}
void
setspawn(uint i, bool on)
{
if (i < (uint)ents.length())
if (i < (uint)ents.count)
ents[i].spawned = on;
}

View file

@ -6,6 +6,7 @@ executable('client',
'ConsoleLine.m',
'Cube.mm',
'DynamicEntity.mm',
'Entity.m',
'Identifier.m',
'KeyMapping.m',
'MD2.mm',
@ -13,6 +14,7 @@ executable('client',
'Menu.m',
'MenuItem.m',
'OFString+Cube.mm',
'PersistentEntity.m',
'Projectile.m',
'ResolverResult.mm',
'ResolverThread.mm',

View file

@ -3,6 +3,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
static OFMutableArray<DynamicEntity *> *monsters;
static int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
@ -121,11 +122,14 @@ monsterclear()
monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0;
} else if (m_classicsp) {
mtimestart = lastmillis;
loopv(ents) if (ents[i].type == MONSTER)
{
DynamicEntity *m = basicmonster(
ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0);
m.o = OFMakeVector3D(ents[i].x, ents[i].y, ents[i].z);
for (Entity *e in ents) {
if (e.type != MONSTER)
continue;
DynamicEntity *m =
basicmonster(e.attr2, e.attr1, M_SLEEP, 100, 0);
m.o = OFMakeVector3D(e.x, e.y, e.z);
entinmap(m);
monstertotal++;
}
@ -377,13 +381,13 @@ monsterthink()
endsp(true);
// equivalent of player entity touch, but only teleports are used
loopv(ents)
{
entity &e = ents[i];
[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
if (e.type != TELEPORT)
continue;
return;
if (OUTBORD(e.x, e.y))
continue;
return;
OFVector3D v =
OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor);
for (DynamicEntity *monster in monsters) {
@ -396,11 +400,12 @@ monsterthink()
v.z += monster.eyeheight;
vdist(dist, t, monster.o, v);
v.z -= monster.eyeheight;
if (dist < 4)
teleport((int)(&e - &ents[0]), monster);
}
teleport(i, monster);
}
}
}];
for (DynamicEntity *monster in monsters)
if (monster.state == CS_ALIVE)

View file

@ -7,6 +7,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "MapModelInfo.h"
// collide with player or monster
@ -58,18 +59,19 @@ cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by,
void
mmcollide(DynamicEntity *d, float &hi, float &lo) // collide with a mapmodel
{
loopv(ents)
{
entity &e = ents[i];
for (Entity *e in ents) {
if (e.type != MAPMODEL)
continue;
MapModelInfo *mmi = getmminfo(e.attr2);
if (mmi == nil || !mmi.h)
continue;
const float r = mmi.rad + d.radius;
if (fabs(e.x - d.o.x) < r && fabs(e.y - d.o.y) < r) {
float mmz =
(float)(S(e.x, e.y)->floor + mmi.zoff + e.attr3);
if (d.o.z - d.eyeheight < mmz) {
if (mmz < hi)
hi = mmz;

View file

@ -121,7 +121,7 @@ extern int findentity(int type, int index = 0);
extern void trigger(int tag, int type, bool savegame);
extern void resettagareas();
extern void settagareas();
extern entity *newentity(
extern Entity *newentity(
int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4);
// worldlight
@ -227,7 +227,7 @@ extern void putint(uchar *&p, int n);
extern int getint(uchar *&p);
extern void sendstring(OFString *t, uchar *&p);
extern void startintermission();
extern void restoreserverstate(vector<entity> &ents);
extern void restoreserverstate(OFArray<Entity *> *ents);
extern uchar *retrieveservers(uchar *buf, int buflen);
extern char msgsizelookup(int msg);
extern void serverms(int mode, int numplayers, int minremain,
@ -257,6 +257,7 @@ extern void monsterpain(DynamicEntity *m, int damage, DynamicEntity *d);
extern void endsp(bool allkilled);
// entities
extern void initEntities();
extern void renderents();
extern void putitems(uchar *&p);
extern void checkquad(int time);

View file

@ -3,6 +3,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
void
line(int x1, int y1, float z1, int x2, int y2, float z2)
@ -175,23 +176,26 @@ OFString *entnames[] = {
@"?",
};
// show sparkly thingies for map entities in edit mode
void
renderents() // show sparkly thingies for map entities in edit mode
renderents()
{
closeent = @"";
if (!editmode)
return;
loopv(ents)
{
entity &e = ents[i];
for (Entity *e in ents) {
if (e.type == NOTUSED)
continue;
OFVector3D v = OFMakeVector3D(e.x, e.y, e.z);
particle_splash(2, 2, 40, v);
}
int e = closestent();
if (e >= 0) {
entity &c = ents[e];
Entity *c = ents[e];
closeent =
[OFString stringWithFormat:@"closest entity = %@ (%d, %d, "
@"%d, %d), selection = (%d, %d)",

View file

@ -4,6 +4,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
#ifdef OF_BIG_ENDIAN
static const int islittleendian = 0;
@ -109,8 +110,9 @@ savestate(OFIRI *IRI)
min(getclientmap().UTF8StringLength, _MAXDEFSTR - 1));
gzwrite(f, map, _MAXDEFSTR);
gzputi(gamemode);
gzputi(ents.length());
loopv(ents) gzputc(f, ents[i].spawned);
gzputi(ents.count);
for (Entity *e in ents)
gzputc(f, e.spawned);
gzwrite(f, data.items, data.count);
OFArray<DynamicEntity *> *monsters = getmonsters();
gzputi(monsters.count);
@ -205,14 +207,16 @@ loadgamerest()
if (demoplayback || !f)
return;
if (gzgeti() != ents.length())
if (gzgeti() != ents.count)
return loadgameout();
loopv(ents)
{
ents[i].spawned = gzgetc(f) != 0;
if (ents[i].type == CARROT && !ents[i].spawned)
trigger(ents[i].attr1, ents[i].attr2, true);
for (Entity *e in ents) {
e.spawned = (gzgetc(f) != 0);
if (e.type == CARROT && !e.spawned)
trigger(e.attr1, e.attr2, true);
}
restoreserverstate(ents);
OFMutableData *data =

View file

@ -4,6 +4,7 @@
#include "cube.h"
#import "Client.h"
#import "Entity.h"
enum { ST_EMPTY, ST_LOCAL, ST_TCPIP };
@ -24,9 +25,9 @@ vector<server_entity> sents;
bool notgotitems = true;
int mode = 0;
// hack: called from savegame code, only works in SP
void
restoreserverstate(
vector<entity> &ents) // hack: called from savegame code, only works in SP
restoreserverstate(OFArray<Entity *> *ents)
{
loopv(sents)
{

View file

@ -3,6 +3,7 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
extern OFString *entnames[]; // lookup from map entities above to strings
@ -53,7 +54,11 @@ void
settagareas()
{
settag(0, 1);
loopv(ents) if (ents[i].type == CARROT) setspawn(i, true);
[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
if (ents[i].type == CARROT)
setspawn(i, true);
}];
} // set for playing
void
@ -256,21 +261,22 @@ closestent() // used for delent and edit mode ent display
{
if (noteditmode())
return -1;
int best;
float bdist = 99999;
loopv(ents)
{
entity &e = ents[i];
__block int best;
__block float bdist = 99999;
[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
if (e.type == NOTUSED)
continue;
return;
OFVector3D v = OFMakeVector3D(e.x, e.y, e.z);
vdist(dist, t, player1.o, v);
if (dist < bdist) {
best = i;
bdist = dist;
}
}
return bdist == 99999 ? -1 : best;
}];
return (bdist == 99999 ? -1 : best);
}
void
@ -319,12 +325,21 @@ findtype(OFString *what)
return NOTUSED;
}
entity *
Entity *
newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4)
{
int type = findtype(what);
persistent_entity e = { (short)x, (short)y, (short)z, (short)v1,
(uchar)type, (uchar)v2, (uchar)v3, (uchar)v4 };
PersistentEntity *e = [PersistentEntity entity];
e.x = x;
e.y = y;
e.z = z;
e.attr1 = v1;
e.type = type;
e.attr2 = v2;
e.attr3 = v3;
e.attr4 = v4;
switch (type) {
case LIGHT:
if (v1 > 32)
@ -345,59 +360,63 @@ newentity(int x, int y, int z, OFString *what, int v1, int v2, int v3, int v4)
e.attr1 = (int)player1.yaw;
break;
}
addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1,
addmsg(1, 10, SV_EDITENT, ents.count, type, e.x, e.y, e.z, e.attr1,
e.attr2, e.attr3, e.attr4);
ents.add(*((entity *)&e)); // unsafe!
[ents addObject:e]; // unsafe!
if (type == LIGHT)
calclight();
return &ents.last();
return e;
}
void
clearents(OFString *name)
{
int type = findtype(name);
if (noteditmode() || multiplayer())
return;
loopv(ents)
{
entity &e = ents[i];
for (Entity *e in ents)
if (e.type == type)
e.type = NOTUSED;
}
if (type == LIGHT)
calclight();
}
COMMAND(clearents, ARG_1STR)
void
scalecomp(uchar &c, int intens)
static uchar
scalecomp(uchar c, int intens)
{
int n = c * intens / 100;
if (n > 255)
n = 255;
c = n;
return n;
}
void
scalelights(int f, int intens)
{
loopv(ents)
{
entity &e = ents[i];
for (Entity *e in ents) {
if (e.type != LIGHT)
continue;
e.attr1 = e.attr1 * f / 100;
if (e.attr1 < 2)
e.attr1 = 2;
if (e.attr1 > 32)
e.attr1 = 32;
if (intens) {
scalecomp(e.attr2, intens);
scalecomp(e.attr3, intens);
scalecomp(e.attr4, intens);
e.attr2 = scalecomp(e.attr2, intens);
e.attr3 = scalecomp(e.attr3, intens);
e.attr4 = scalecomp(e.attr4, intens);
}
}
calclight();
}
COMMAND(scalelights, ARG_2INT)
@ -405,7 +424,7 @@ COMMAND(scalelights, ARG_2INT)
int
findentity(int type, int index)
{
for (int i = index; i < ents.length(); i++)
for (int i = index; i < ents.count; i++)
if (ents[i].type == type)
return i;
loopj(index) if (ents[j].type == type) return j;
@ -474,10 +493,10 @@ empty_world(int factor, bool force)
*S(x + ssize / 4, y + ssize / 4) =
*SWS(oldworld, x, y, ssize / 2);
}
loopv(ents)
{
ents[i].x += ssize / 4;
ents[i].y += ssize / 4;
for (Entity *e in ents) {
e.x += ssize / 4;
e.y += ssize / 4;
}
} else {
char buffer[128] = "Untitled Map by Unknown";
@ -485,7 +504,7 @@ empty_world(int factor, bool force)
hdr.waterlevel = -100000;
loopi(15) hdr.reserved[i] = 0;
loopk(3) loopi(256) hdr.texlists[k][i] = i;
ents.setsize(0);
[ents removeAllObjects];
block b = { 8, 8, ssize - 16, ssize - 16 };
edittypexy(SPACE, b);
}

View file

@ -2,6 +2,15 @@
#include "cube.h"
#import "Entity.h"
struct persistent_entity {
short x, y, z; // cube aligned position
short attr1;
uchar type; // type is one of the above
uchar attr2, attr3, attr4;
};
void
backup(OFString *name, OFString *backupname)
{
@ -173,15 +182,17 @@ save_world(OFString *mname)
}
hdr.version = MAPVERSION;
hdr.numents = 0;
loopv(ents) if (ents[i].type != NOTUSED) hdr.numents++;
for (Entity *e in ents)
if (e.type != NOTUSED)
hdr.numents++;
header tmp = hdr;
endianswap(&tmp.version, sizeof(int), 4);
endianswap(&tmp.waterlevel, sizeof(int), 16);
gzwrite(f, &tmp, sizeof(header));
loopv(ents)
{
if (ents[i].type != NOTUSED) {
entity tmp = ents[i];
for (Entity *e in ents) {
if (e.type != NOTUSED) {
struct persistent_entity tmp = { e.x, e.y, e.z, e.attr1,
e.type, e.attr2, e.attr3, e.attr4 };
endianswap(&tmp, sizeof(short), 4);
gzwrite(f, &tmp, sizeof(persistent_entity));
}
@ -273,13 +284,24 @@ load_world(OFString *mname) // still supports all map formats that have existed
} else {
hdr.waterlevel = -100000;
}
ents.setsize(0);
[ents removeAllObjects];
loopi(hdr.numents)
{
entity &e = ents.add();
gzread(f, &e, sizeof(persistent_entity));
endianswap(&e, sizeof(short), 4);
e.spawned = false;
struct persistent_entity tmp;
gzread(f, &tmp, sizeof(persistent_entity));
endianswap(&tmp, sizeof(short), 4);
Entity *e = [Entity entity];
e.x = tmp.x;
e.y = tmp.y;
e.z = tmp.z;
e.attr1 = tmp.attr1;
e.type = tmp.type;
e.attr2 = tmp.attr2;
e.attr3 = tmp.attr3;
e.attr4 = tmp.attr4;
[ents addObject:e];
if (e.type == LIGHT) {
if (!e.attr2)
e.attr2 = 255; // needed for MAPVERSION<=2

View file

@ -3,14 +3,16 @@
#include "cube.h"
#import "DynamicEntity.h"
#import "Entity.h"
#import "PersistentEntity.h"
extern bool hasoverbright;
VAR(lightscale, 1, 4, 100);
// done in realtime, needs to be fast
void
lightray(float bx, float by,
persistent_entity &light) // done in realtime, needs to be fast
lightray(float bx, float by, PersistentEntity *light)
{
float lx = light.x + (rnd(21) - 10) * 0.1f;
float ly = light.y + (rnd(21) - 10) * 0.1f;
@ -121,7 +123,7 @@ lightray(float bx, float by,
}
void
calclightsource(persistent_entity &l)
calclightsource(PersistentEntity *l)
{
int reach = l.attr1;
int sx = l.x - reach;
@ -175,12 +177,9 @@ calclight()
s->r = s->g = s->b = 10;
}
loopv(ents)
{
entity &e = ents[i];
for (Entity *e in ents)
if (e.type == LIGHT)
calclightsource(e);
}
block b = { 1, 1, ssize - 2, ssize - 2 };
postlightarea(b);
@ -236,8 +235,13 @@ dodynlight(const OFVector3D &vold, const OFVector3D &v, int reach, int strength,
block *copy = blockcopy(b);
[dlights addItem:&copy];
persistent_entity l = { (short)v.x, (short)v.y, (short)v.z,
(short)reach, LIGHT, (uchar)strength, 0, 0 };
PersistentEntity *l = [Entity entity];
l.x = v.x;
l.y = v.y;
l.z = v.z;
l.attr1 = reach;
l.type = LIGHT;
l.attr2 = strength;
calclightsource(l);
postlightarea(b);
}