Cube  Diff

Differences From Artifact [7586e1be61]:

To Artifact [aab23b54fe]:


1
2
3
4
5
6
7
8
9



10
11







12
13
14
15
16

17
18
19
20




21
22
23

24
25
26
27
28
29
30
31
32
















33
34

35

36
37

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66





























67
68

69

70
71
72
73







74
75

76


77
78
79
80
81
82
83






84
85
86


87
88

89
90
91
92
93
94
95
96
97
98
99
100












101
102


103

104
105
106
107
108
109
110
111
112
113











114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130




















131

132
133


134
135
136



137
138
139
140
141
142
143






144


145

146
147
148
149
150




151
152

153

154

155
156



157
158


159

160

161
162
163
164
165
166









167
168

169
170
171
172




173
174
175


176
177
178
179
180
181
182








183
184
185
186
187
188
189
190
191
192
193

















194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210




















211
212
213
214
215
216
217
218
219
220









221
222
223
224
225
226
227






228
229
230
231




232
233
234
235
236







237
238
239
240
241
242
243









244
245
246
247
248
249
250
251
252










253
254

255
256

257

258
259
260
261
262
263
264
265
266











267
268
269
270
271
272
273
274








275
276
277
278
279
280
281
282








283
284

285
286
287


288
289

290

291

292
293
294
295





296
297

298

299
300

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317





















318
319
320
321
322
323
324
325
326
327
328
329
330
331
332













333
334

335

336
337



338
1
2
3
4
5
6
7
8
9
10
11
12


13
14
15
16
17
18
19
20
21
22
23

24
25



26
27
28
29
30
31

32









33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

52
53

54





























55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

87
88



89
90
91
92
93
94
95
96
97
98

99
100
101






102
103
104
105
106
107



108
109


110












111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

127
128









129
130
131
132
133
134
135
136
137
138
139

















140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161


162
163
164


165
166
167
168
169





170
171
172
173
174
175
176
177
178

179
180




181
182
183
184
185
186
187

188
189
190


191
192
193
194
195
196
197

198
199
200






201
202
203
204
205
206
207
208
209


210




211
212
213
214
215


216
217
218






219
220
221
222
223
224
225
226











227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243

















244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263










264
265
266
267
268
269
270
271
272







273
274
275
276
277
278
279



280
281
282
283





284
285
286
287
288
289
290







291
292
293
294
295
296
297
298
299









300
301
302
303
304
305
306
307
308
309
310

311
312
313
314

315
316








317
318
319
320
321
322
323
324
325
326
327








328
329
330
331
332
333
334
335








336
337
338
339
340
341
342
343


344



345
346
347
348
349

350
351
352




353
354
355
356
357
358
359
360

361
362

363

















364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384

385
386
387











388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403

404
405

406
407
408
409









+
+
+
-
-
+
+
+
+
+
+
+




-
+

-
-
-
+
+
+
+


-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+
-
+

-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+
-
+

-
-
-
+
+
+
+
+
+
+


+
-
+
+

-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+


+
+
-
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
-
-
+
+

-
-
+
+
+


-
-
-
-
-
+
+
+
+
+
+

+
+
-
+

-
-
-
-
+
+
+
+


+
-
+

+
-
-
+
+
+


+
+
-
+

+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
+
+
+
+

-
-
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+

-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

-
+


+
-
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
-
-
-
+
+


+
-
+

+
-
-
-
-
+
+
+
+
+


+
-
+

-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-



-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+


+
-
+

-
+
+
+

// monster.cpp: implements AI for single player monsters, currently client only

#include "cube.h"

dvector monsters;
int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;

VARF(skill, 1, 3, 10, conoutf("skill is now %d", skill));

dvector &
getmonsters()
{
dvector &getmonsters() { return monsters; };
void restoremonsterstate() { loopv(monsters) if(monsters[i]->state==CS_DEAD) numkilled++; };        // for savegames
	return monsters;
};
void
restoremonsterstate()
{
	loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++;
}; // for savegames

#define TOTMFREQ 13
#define NUMMONSTERTYPES 8

struct monstertype      // see docs for how these values modify behaviour
struct monstertype // see docs for how these values modify behaviour
{
    short gun, speed, health, freq, lag, rate, pain, loyalty, mscale, bscale;
    short painsound, diesound;
    char *name, *mdlname;
	short gun, speed, health, freq, lag, rate, pain, loyalty, mscale,
	    bscale;
	short painsound, diesound;
	char *name, *mdlname;
}

monstertypes[NUMMONSTERTYPES] =
monstertypes[NUMMONSTERTYPES] = {
{
    { GUN_FIREBALL,  15, 100, 3, 0,   100, 800, 1, 10, 10, S_PAINO, S_DIE1,   "an ogre",     "monster/ogro"    },
    { GUN_CG,        18,  70, 2, 70,   10, 400, 2,  8,  9, S_PAINR, S_DEATHR, "a rhino",     "monster/rhino"   },
    { GUN_SG,        14, 120, 1, 100, 300, 400, 4, 14, 14, S_PAINE, S_DEATHE, "ratamahatta", "monster/rat"     },
    { GUN_RIFLE,     15, 200, 1, 80,  300, 300, 4, 18, 18, S_PAINS, S_DEATHS, "a slith",     "monster/slith"   },
    { GUN_RL,        13, 500, 1, 0,   100, 200, 6, 24, 24, S_PAINB, S_DEATHB, "bauul",       "monster/bauul"   },
    { GUN_BITE,      22,  50, 3, 0,   100, 400, 1, 12, 15, S_PAINP, S_PIGGR2, "a hellpig",   "monster/hellpig" },
    { GUN_ICEBALL,   12, 250, 1, 0,    10, 400, 6, 18, 18, S_PAINH, S_DEATHH, "a knight",    "monster/knight"  },
    { GUN_SLIMEBALL, 15, 100, 1, 0,   200, 400, 2, 13, 10, S_PAIND, S_DEATHD, "a goblin",    "monster/goblin"  },
    {GUN_FIREBALL, 15, 100, 3, 0, 100, 800, 1, 10, 10, S_PAINO, S_DIE1,
        "an ogre", "monster/ogro"},
    {GUN_CG, 18, 70, 2, 70, 10, 400, 2, 8, 9, S_PAINR, S_DEATHR, "a rhino",
        "monster/rhino"},
    {GUN_SG, 14, 120, 1, 100, 300, 400, 4, 14, 14, S_PAINE, S_DEATHE,
        "ratamahatta", "monster/rat"},
    {GUN_RIFLE, 15, 200, 1, 80, 300, 300, 4, 18, 18, S_PAINS, S_DEATHS,
        "a slith", "monster/slith"},
    {GUN_RL, 13, 500, 1, 0, 100, 200, 6, 24, 24, S_PAINB, S_DEATHB, "bauul",
        "monster/bauul"},
    {GUN_BITE, 22, 50, 3, 0, 100, 400, 1, 12, 15, S_PAINP, S_PIGGR2,
        "a hellpig", "monster/hellpig"},
    {GUN_ICEBALL, 12, 250, 1, 0, 10, 400, 6, 18, 18, S_PAINH, S_DEATHH,
        "a knight", "monster/knight"},
    {GUN_SLIMEBALL, 15, 100, 1, 0, 200, 400, 2, 13, 10, S_PAIND, S_DEATHD,
        "a goblin", "monster/goblin"},
};

dynent *
dynent *basicmonster(int type, int yaw, int state, int trigger, int move)
basicmonster(int type, int yaw, int state, int trigger, int move)
{
    if(type>=NUMMONSTERTYPES)
	if (type >= NUMMONSTERTYPES) {
    {
        conoutf("warning: unknown monster in spawn: %d", type);
        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;
    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;
    strcpy_s(m->name, t->name);
    monsters.add(m);
    return m;
		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;
	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;
	strcpy_s(m->name, t->name);
	monsters.add(m);
	return m;
};

void
void spawnmonster()     // spawn a random monster according to freq distribution in DMSP
spawnmonster() // spawn a random monster according to freq distribution in DMSP
{
    int n = rnd(TOTMFREQ), type;
    for(int i = 0; ; i++) if((n -= monstertypes[i].freq)<0) { type = i; break; };
    basicmonster(type, rnd(360), M_SEARCH, 1000, 1);
	int n = rnd(TOTMFREQ), type;
	for (int i = 0;; i++)
		if ((n -= monstertypes[i].freq) < 0) {
			type = i;
			break;
		};
	basicmonster(type, rnd(360), M_SEARCH, 1000, 1);
};

void
void monsterclear()     // called after map start of when toggling edit mode to reset/spawn all monsters to initial state
monsterclear() // called after map start of when toggling edit mode to
               // reset/spawn all monsters to initial state
{
    loopv(monsters) gp()->dealloc(monsters[i], sizeof(dynent)); 
    monsters.setsize(0);
    numkilled = 0;
    monstertotal = 0;
    spawnremain = 0;
    if(m_dmsp)
	loopv(monsters) gp()->dealloc(monsters[i], sizeof(dynent));
	monsters.setsize(0);
	numkilled = 0;
	monstertotal = 0;
	spawnremain = 0;
	if (m_dmsp) {
    {
        nextmonster = mtimestart = lastmillis+10000;
        monstertotal = spawnremain = gamemode<0 ? skill*10 : 0;
		nextmonster = mtimestart = lastmillis + 10000;
		monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0;
    }
    else if(m_classicsp)
	} else if (m_classicsp) {
    {
        mtimestart = lastmillis;
        loopv(ents) if(ents[i].type==MONSTER)
        {
            dynent *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;
            entinmap(m);
            monstertotal++;
        };
    };
		mtimestart = lastmillis;
		loopv(ents) if (ents[i].type == MONSTER)
		{
			dynent *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;
			entinmap(m);
			monstertotal++;
		};
	};
};

bool
los(float lx, float ly, float lz, float bx, float by, float bz,
bool los(float lx, float ly, float lz, float bx, float by, float bz, vec &v) // height-correct line of sight for monster shooting/seeing
    vec &v) // height-correct line of sight for monster shooting/seeing
{
    if(OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by)) return false;
    float dx = bx-lx;
    float dy = by-ly; 
    int steps = (int)(sqrt(dx*dx+dy*dy)/0.9);
    if(!steps) return false;
    float x = lx;
    float y = ly;
    int i = 0;
    for(;;)
	if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by))
		return false;
	float dx = bx - lx;
	float dy = by - ly;
	int steps = (int)(sqrt(dx * dx + dy * dy) / 0.9);
	if (!steps)
		return false;
	float x = lx;
	float y = ly;
	int i = 0;
	for (;;) {
    {
        sqr *s = S(fast_f2nat(x), fast_f2nat(y));
        if(SOLID(s)) break;
        float floor = s->floor;
        if(s->type==FHF) floor -= s->vdelta/4.0f;
        float ceil = s->ceil;
        if(s->type==CHF) ceil += s->vdelta/4.0f;
        float rz = lz-((lz-bz)*(i/(float)steps));
        if(rz<floor || rz>ceil) break;
        v.x = x;
        v.y = y;
        v.z = rz;
        x += dx/(float)steps;
        y += dy/(float)steps;
        i++;
    };
    return i>=steps;
		sqr *s = S(fast_f2nat(x), fast_f2nat(y));
		if (SOLID(s))
			break;
		float floor = s->floor;
		if (s->type == FHF)
			floor -= s->vdelta / 4.0f;
		float ceil = s->ceil;
		if (s->type == CHF)
			ceil += s->vdelta / 4.0f;
		float rz = lz - ((lz - bz) * (i / (float)steps));
		if (rz < floor || rz > ceil)
			break;
		v.x = x;
		v.y = y;
		v.z = rz;
		x += dx / (float)steps;
		y += dy / (float)steps;
		i++;
	};
	return i >= steps;
};


bool enemylos(dynent *m, vec &v)
bool
enemylos(dynent *m, vec &v)
{
    v = m->o;
    return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y, m->enemy->o.z, v);
	v = m->o;
	return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y,
	    m->enemy->o.z, v);
};

// monster AI is sequenced using transitions: they are in a particular state where
// they execute a particular behaviour until the trigger time is hit, and then they
// reevaluate their situation based on the current state, the environment etc., and
// transition to the next state. Transition timeframes are parametrized by difficulty
// level (skill), faster transitions means quicker decision making means tougher AI.
// monster AI is sequenced using transitions: they are in a particular state
// where they execute a particular behaviour until the trigger time is hit, and
// then they reevaluate their situation based on the current state, the
// environment etc., and transition to the next state. Transition timeframes are
// parametrized by difficulty level (skill), faster transitions means quicker
// decision making means tougher AI.

void
transition(dynent *m, int state, int moving, int n,
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
    int r) // n = at skill 0, n/2 = at skill 10, r = added random factor
{
    m->monsterstate = state;
    m->move = moving;
    n = n*130/100;
    m->trigger = lastmillis+n-skill*(n/16)+rnd(r+1);
	m->monsterstate = state;
	m->move = moving;
	n = n * 130 / 100;
	m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
};

void
void normalise(dynent *m, float angle)
normalise(dynent *m, float angle)
{
	while (m->yaw < angle - 180.0f)
    while(m->yaw<angle-180.0f) m->yaw += 360.0f;
    while(m->yaw>angle+180.0f) m->yaw -= 360.0f;
		m->yaw += 360.0f;
	while (m->yaw > angle + 180.0f)
		m->yaw -= 360.0f;
};

void
monsteraction(
void monsteraction(dynent *m)           // main AI thinking routine, called every frame for every monster
    dynent *m) // main AI thinking routine, called every frame for every monster
{
	if (m->enemy->state == CS_DEAD) {
    if(m->enemy->state==CS_DEAD) { m->enemy = player1; m->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;
		m->enemy = player1;
		m->anger = 0;
	};
	normalise(m, m->targetyaw);
	if (m->targetyaw > m->yaw) // slowly turn monster towards his target
	{
		m->yaw += curtime * 0.5f;
		if (m->targetyaw < m->yaw)
			m->yaw = m->targetyaw;
    }
    else
	} 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;

    if(m->blocked)                                                              // special case: if we run into scenery
    {
        m->blocked = false;
        if(!rnd(20000/monstertypes[m->mtype].speed))                            // try to jump over obstackle (rare)
        {
            m->jumpnext = true;
	if (m->blocked) // special case: if we run into scenery
	{
		m->blocked = false;
		if (!rnd(20000 /
		         monstertypes[m->mtype]
		             .speed)) // try to jump over obstackle (rare)
		{
			m->jumpnext = true;
        }
        else if(m->trigger<lastmillis && (m->monsterstate!=M_HOME || !rnd(5)))  // search for a way around (common)
        {
            m->targetyaw += 180+rnd(180);                                       // patented "random walk" AI pathfinding (tm) ;)
            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+180;
    
    switch(m->monsterstate)
		} else if (m->trigger < lastmillis &&
		           (m->monsterstate != M_HOME ||
		               !rnd(5))) // search for a way around (common)
		{
			m->targetyaw +=
			    180 + rnd(180); // patented "random walk" AI
			                    // pathfinding (tm) ;)
			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 +
	    180;

	switch (m->monsterstate) {
    {
        case M_PAIN:
        case M_ATTACKING:
        case M_SEARCH:
            if(m->trigger<lastmillis) transition(m, M_HOME, 1, 100, 200);
            break;
            
        case M_SLEEP:                       // state classic sp monster start in, wait for visual contact
        {
            vec target;
            if(editmode || !enemylos(m, target)) return;   // skip running physics
            normalise(m, enemyyaw);
            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)
	case M_PAIN:
	case M_ATTACKING:
	case M_SEARCH:
		if (m->trigger < lastmillis)
			transition(m, M_HOME, 1, 100, 200);
		break;

	case M_SLEEP: // state classic sp monster start in, wait for visual
	              // contact
	{
		vec target;
		if (editmode || !enemylos(m, target))
			return; // skip running physics
		normalise(m, enemyyaw);
		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) {
            || angle<10)
            {
                transition(m, M_HOME, 1, 500, 200);
                playsound(S_GRUNT1+rnd(2), &m->o);
            };
            break;
        };
        
        case M_AIMING:                      // this state is the delay between wanting to shoot and actually firing
            if(m->trigger<lastmillis)
			transition(m, M_HOME, 1, 500, 200);
			playsound(S_GRUNT1 + rnd(2), &m->o);
		};
		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);
                transition(m, M_ATTACKING, 0, 600, 0);
            };
            break;
			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) {
            {
                vec target;
                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);
			vec target;
			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
                {
                    if(!rnd((int)disttoenemy/3+1) && m->enemy->state==CS_ALIVE)         // get ready to fire
                    { 
                        m->attacktarget = target;
                        transition(m, M_AIMING, 0, monstertypes[m->mtype].lag, 10);
			} 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;
					transition(m, M_AIMING, 0,
                    }
                    else                                                                // track player some more
                    {
                        transition(m, M_HOME, 1, monstertypes[m->mtype].rate, 0);
                    };
                };
            };
            break;
    };
					    monstertypes[m->mtype].lag, 10);
				} else // track player some more
				{
					transition(m, M_HOME, 1,
					    monstertypes[m->mtype].rate, 0);
				};
			};
		};
		break;
	};

    moveplayer(m, 1, false);        // use physics to move monster
	moveplayer(m, 1, false); // use physics to move monster
};

void
void monsterpain(dynent *m, int damage, dynent *d)
monsterpain(dynent *m, int damage, dynent *d)
{
    if(d->monsterstate)     // a monster hit us
    {
        if(m!=d)            // guard for RL guys shooting themselves :)
        {
            m->anger++;     // don't attack straight away, first get angry
            int anger = m->mtype==d->mtype ? m->anger/2 : m->anger;
            if(anger>=monstertypes[m->mtype].loyalty) m->enemy = d;     // monster infight if very angry
        };
	if (d->monsterstate) // a monster hit us
	{
		if (m != d) // guard for RL guys shooting themselves :)
		{
			m->anger++; // don't attack straight away, first get
			            // angry
			int anger =
			    m->mtype == d->mtype ? m->anger / 2 : m->anger;
			if (anger >= monstertypes[m->mtype].loyalty)
				m->enemy = d; // monster infight if very angry
		};
    }
    else                    // player hit us
    {
        m->anger = 0;
        m->enemy = d;
    };
    transition(m, M_PAIN, 0, monstertypes[m->mtype].pain,200);      // in this state monster won't attack
    if((m->health -= damage)<=0)
	} else // player hit us
	{
		m->anger = 0;
		m->enemy = d;
	};
	transition(m, M_PAIN, 0, monstertypes[m->mtype].pain,
	    200); // in this state monster won't attack
	if ((m->health -= damage) <= 0) {
    {
        m->state = CS_DEAD;
        m->lastaction = lastmillis;
        numkilled++;
        player1->frags = numkilled;
        playsound(monstertypes[m->mtype].diesound, &m->o);
        int remain = monstertotal-numkilled;
        if(remain>0 && remain<=5) conoutf("only %d monster(s) remaining", remain);
		m->state = CS_DEAD;
		m->lastaction = lastmillis;
		numkilled++;
		player1->frags = numkilled;
		playsound(monstertypes[m->mtype].diesound, &m->o);
		int remain = monstertotal - numkilled;
		if (remain > 0 && remain <= 5)
			conoutf("only %d monster(s) remaining", remain);
    }
    else
	} else {
    {
        playsound(monstertypes[m->mtype].painsound, &m->o);
    };
		playsound(monstertypes[m->mtype].painsound, &m->o);
	};
};

void
void endsp(bool allkilled)
endsp(bool allkilled)
{
	conoutf(
    conoutf(allkilled ? "you have cleared the map!" : "you reached the exit!");
    conoutf("score: %d kills in %d seconds", numkilled, (lastmillis-mtimestart)/1000);
    monstertotal = 0;
    startintermission();
	    allkilled ? "you have cleared the map!" : "you reached the exit!");
	conoutf("score: %d kills in %d seconds", numkilled,
	    (lastmillis - mtimestart) / 1000);
	monstertotal = 0;
	startintermission();
};

void
void monsterthink()
monsterthink()
{
    if(m_dmsp && spawnremain && lastmillis>nextmonster)
	if (m_dmsp && spawnremain && lastmillis > nextmonster) {
    {
        if(spawnremain--==monstertotal) conoutf("The invasion has begun!");
        nextmonster = lastmillis+1000;
        spawnmonster();
    };
    
    if(monstertotal && !spawnremain && numkilled==monstertotal) endsp(true);
    
    loopv(ents)             // equivalent of player entity touch, but only teleports are used
    {
        entity &e = ents[i];
        if(e.type!=TELEPORT) continue;
        if(OUTBORD(e.x, e.y)) continue;
        vec v = { e.x, e.y, S(e.x, e.y)->floor };
        loopv(monsters) if(monsters[i]->state==CS_DEAD)
        {
			if(lastmillis-monsters[i]->lastaction<2000)
		if (spawnremain-- == monstertotal)
			conoutf("The invasion has begun!");
		nextmonster = lastmillis + 1000;
		spawnmonster();
	};

	if (monstertotal && !spawnremain && numkilled == monstertotal)
		endsp(true);

	loopv(ents) // equivalent of player entity touch, but only teleports are
	            // used
	{
		entity &e = ents[i];
		if (e.type != TELEPORT)
			continue;
		if (OUTBORD(e.x, e.y))
			continue;
		vec v = {e.x, e.y, 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);
			};
        }
        else
        {
            v.z += monsters[i]->eyeheight;
            vdist(dist, t, monsters[i]->o, v);
            v.z -= monsters[i]->eyeheight;
            if(dist<4) teleport((int)(&e-&ents[0]), monsters[i]);
        };
    };
    
    loopv(monsters) if(monsters[i]->state==CS_ALIVE) monsteraction(monsters[i]);
		}
		else
		{
			v.z += monsters[i]->eyeheight;
			vdist(dist, t, monsters[i]->o, v);
			v.z -= monsters[i]->eyeheight;
			if (dist < 4)
				teleport((int)(&e - &ents[0]), monsters[i]);
		};
	};

	loopv(monsters) if (monsters[i]->state == CS_ALIVE)
	    monsteraction(monsters[i]);
};

void
void monsterrender()
monsterrender()
{
    loopv(monsters) renderclient(monsters[i], false, monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype==5, monstertypes[monsters[i]->mtype].mscale/10.0f);
	loopv(monsters) renderclient(monsters[i], false,
	    monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5,
	    monstertypes[monsters[i]->mtype].mscale / 10.0f);
};