1 // capture.h: client and server state for capture gamemode
5 static const int CAPTURERADIUS
= 64;
6 static const int CAPTUREHEIGHT
= 24;
7 static const int OCCUPYPOINTS
= 15;
8 static const int OCCUPYLIMIT
= 100;
9 static const int CAPTURESCORE
= 1;
10 static const int SCORESECS
= 10;
11 static const int AMMOSECS
= 15;
12 static const int REGENSECS
= 1;
13 static const int REGENHEALTH
= 10;
14 static const int REGENARMOUR
= 10;
15 static const int REGENAMMO
= 20;
16 static const int MAXAMMO
= 5;
17 static const int REPAMMODIST
= 32;
18 static const int RESPAWNSECS
= 10;
28 int ammotype
, ammo
, owners
, enemies
, converted
, capturetime
;
30 baseinfo() { reset(); }
49 bool enter(const char *team
)
51 if(!strcmp(owner
, team
))
58 if(strcmp(enemy
, team
))
61 s_strcpy(enemy
, team
);
66 else if(strcmp(enemy
, team
)) return false;
71 bool steal(const char *team
)
73 return !enemies
&& strcmp(owner
, team
);
76 bool leave(const char *team
)
78 if(!strcmp(owner
, team
))
83 if(strcmp(enemy
, team
)) return false;
88 int occupy(const char *team
, int units
)
90 if(strcmp(enemy
, team
)) return -1;
94 if(converted
<=0) noenemy();
97 else if(converted
<(owner
[0] ? 2 : 1)*OCCUPYLIMIT
) return -1;
98 if(owner
[0]) { owner
[0] = '\0'; converted
= 0; s_strcpy(enemy
, team
); return 0; }
99 else { s_strcpy(owner
, team
); ammo
= 0; capturetime
= 0; owners
= enemies
; noenemy(); return 1; }
104 if(ammo
>=MAXAMMO
) return false;
105 ammo
= min(ammo
+i
, MAXAMMO
);
109 bool takeammo(const char *team
)
111 if(strcmp(owner
, team
) || ammo
<=0) return false;
117 vector
<baseinfo
> bases
;
125 vector
<score
> scores
;
129 capturestate() : captures(0) {}
138 score
&findscore(const char *team
)
142 score
&cs
= scores
[i
];
143 if(!strcmp(cs
.team
, team
)) return cs
;
145 score
&cs
= scores
.add();
146 s_strcpy(cs
.team
, team
);
151 void addbase(int ammotype
, const vec
&o
)
153 baseinfo
&b
= bases
.add();
154 b
.ammotype
= ammotype
? ammotype
: rnd(5)+1;
158 void initbase(int i
, int ammotype
, const char *owner
, const char *enemy
, int converted
, int ammo
)
160 if(!bases
.inrange(i
)) return;
161 baseinfo
&b
= bases
[i
];
162 b
.ammotype
= ammotype
;
163 s_strcpy(b
.owner
, owner
);
164 s_strcpy(b
.enemy
, enemy
);
165 b
.converted
= converted
;
169 bool hasbases(const char *team
)
173 baseinfo
&b
= bases
[i
];
174 if(b
.owner
[0] && !strcmp(b
.owner
, team
)) return true;
179 float disttoenemy(baseinfo
&b
)
184 baseinfo
&e
= bases
[i
];
185 if(e
.owner
[0] && strcmp(b
.owner
, e
.owner
))
186 dist
= min(dist
, b
.o
.dist(e
.o
));
191 bool insidebase(const baseinfo
&b
, const vec
&o
)
193 float dx
= (b
.o
.x
-o
.x
), dy
= (b
.o
.y
-o
.y
), dz
= (b
.o
.z
-o
.z
+14);
194 return dx
*dx
+ dy
*dy
<= CAPTURERADIUS
*CAPTURERADIUS
&& fabs(dz
) <= CAPTUREHEIGHT
;
200 struct captureclient
: capturestate
205 captureclient(fpsclient
&cl
) : cl(cl
), radarscale(0)
207 CCOMMAND(repammo
, "", (captureclient
*self
), self
->replenishammo());
212 int gamemode
= cl
.gamemode
;
213 if(m_noitems
) return;
216 baseinfo
&b
= bases
[i
];
217 if(b
.ammotype
>0 && b
.ammotype
<=I_CARTRIDGES
-I_SHELLS
+1 && insidebase(b
, cl
.player1
->o
) && cl
.player1
->hasmaxammo(b
.ammotype
-1+I_SHELLS
)) return;
219 cl
.cc
.addmsg(SV_REPAMMO
, "r");
222 void receiveammo(int type
)
225 if(type
<I_SHELLS
|| type
>I_CARTRIDGES
) return;
226 cl
.et
.repammo(cl
.player1
, type
);
231 int gamemode
= cl
.gamemode
;
234 baseinfo
&b
= bases
[i
];
235 const char *flagname
= b
.owner
[0] ? (strcmp(b
.owner
, cl
.player1
->team
) ? "flags/red" : "flags/blue") : "flags/neutral";
236 rendermodel(b
.ent
->color
, b
.ent
->dir
, flagname
, ANIM_MAPMODEL
|ANIM_LOOP
, 0, 0, b
.o
, 0, 0, 0, 0, NULL
, MDL_SHADOW
| MDL_CULL_VFC
| MDL_CULL_OCCLUDED
);
237 if(b
.ammotype
>0 && b
.ammotype
<=I_CARTRIDGES
-I_SHELLS
+1) loopi(m_noitemsrail
? 0 : (m_noitems
? 1 : b
.ammo
))
239 float angle
= 2*M_PI
*(cl
.lastmillis
/4000.0f
+ i
/float(MAXAMMO
));
241 p
.x
+= 10*cosf(angle
);
242 p
.y
+= 10*sinf(angle
);
244 rendermodel(b
.ent
->color
, b
.ent
->dir
, cl
.et
.entmdlname(I_SHELLS
+b
.ammotype
-1), ANIM_MAPMODEL
|ANIM_LOOP
, 0, 0, p
, 0, 0, 0, 0, NULL
, MDL_SHADOW
| MDL_CULL_VFC
| MDL_CULL_OCCLUDED
);
246 int ttype
= 11, mtype
= -1;
249 bool isowner
= !strcmp(b
.owner
, cl
.player1
->team
);
252 s_sprintf(b
.info
)("\f%d%s \f0vs. \f%d%s", isowner
? 3 : 1, b
.enemy
, isowner
? 1 : 3, b
.owner
);
253 mtype
= isowner
? 19 : 20;
255 else { s_sprintf(b
.info
)("%s", b
.owner
); ttype
= isowner
? 16 : 13; }
259 s_sprintf(b
.info
)("%s", b
.enemy
);
260 if(strcmp(b
.enemy
, cl
.player1
->team
)) { ttype
= 13; mtype
= 17; }
261 else { ttype
= 16; mtype
= 18; }
263 else b
.info
[0] = '\0';
265 abovemodel(above
, flagname
);
267 particle_text(above
, b
.info
, ttype
, 1);
271 particle_meter(above
, b
.converted
/float((b
.owner
[0] ? 2 : 1) * OCCUPYLIMIT
), mtype
, 1);
276 void drawradar(float x
, float y
, float s
)
278 glTexCoord2f(0.0f
, 0.0f
); glVertex2f(x
, y
);
279 glTexCoord2f(1.0f
, 0.0f
); glVertex2f(x
+s
, y
);
280 glTexCoord2f(1.0f
, 1.0f
); glVertex2f(x
+s
, y
+s
);
281 glTexCoord2f(0.0f
, 1.0f
); glVertex2f(x
, y
+s
);
284 void drawblips(int x
, int y
, int s
, int type
, bool skipenemy
= false)
286 const char *textures
[3] = {"data/blip_red.png", "data/blip_grey.png", "data/blip_blue.png"};
287 settexture(textures
[max(type
+1, 0)]);
289 float scale
= radarscale
<=0 || radarscale
>cl
.maxradarscale() ? cl
.maxradarscale() : radarscale
;
292 baseinfo
&b
= bases
[i
];
293 if(skipenemy
&& b
.enemy
[0]) continue;
296 case 1: if(!b
.owner
[0] || strcmp(b
.owner
, cl
.player1
->team
)) continue; break;
297 case 0: if(b
.owner
[0]) continue; break;
298 case -1: if(!b
.owner
[0] || !strcmp(b
.owner
, cl
.player1
->team
)) continue; break;
299 case -2: if(!b
.enemy
[0] || !strcmp(b
.enemy
, cl
.player1
->team
)) continue; break;
302 dir
.sub(cl
.player1
->o
);
304 float dist
= dir
.magnitude();
305 if(dist
>= scale
) dir
.mul(scale
/dist
);
306 dir
.rotate_around_z(-cl
.player1
->yaw
*RAD
);
307 drawradar(x
+ s
*0.5f
*0.95f
*(1.0f
+dir
.x
/scale
), y
+ s
*0.5f
*0.95f
*(1.0f
+dir
.y
/scale
), 0.05f
*s
);
314 int gamemode
= cl
.gamemode
;
315 if(m_regencapture
) return -1;
316 return max(0, (m_noitemsrail
? RESPAWNSECS
/2 : RESPAWNSECS
)-(cl
.lastmillis
-cl
.player1
->lastpain
)/1000);
319 void capturehud(int w
, int h
)
322 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
323 int x
= 1800*w
/h
*34/40, y
= 1800*1/40, s
= 1800*w
/h
*5/40;
325 settexture("data/radar.png");
327 drawradar(float(x
), float(y
), float(s
));
329 bool showenemies
= cl
.lastmillis
%1000 >= 500;
330 drawblips(x
, y
, s
, 1, showenemies
);
331 drawblips(x
, y
, s
, 0, showenemies
);
332 drawblips(x
, y
, s
, -1, showenemies
);
333 if(showenemies
) drawblips(x
, y
, s
, -2);
334 if(cl
.player1
->state
== CS_DEAD
)
336 int wait
= respawnwait();
341 glOrtho(0, w
*900/h
, 900, 0, -1, 1);
342 draw_textf("%d", (x
+s
/2)/2-(wait
>=10 ? 28 : 16), (y
+s
/2)/2-32, wait
);
354 extentity
*e
= cl
.et
.ents
[i
];
355 if(e
->type
!=BASE
) continue;
356 baseinfo
&b
= bases
.add();
358 b
.ammotype
= e
->attr1
;
359 s_sprintfd(alias
)("base_%d", e
->attr2
);
360 const char *name
= getalias(alias
);
361 if(name
[0]) s_strcpy(b
.name
, name
); else s_sprintf(b
.name
)("base %d", bases
.length());
365 loopv(bases
) center
.add(bases
[i
].o
);
366 center
.div(bases
.length());
368 loopv(bases
) radarscale
= max(radarscale
, 2*center
.dist(bases
[i
].o
));
371 void sendbases(ucharbuf
&p
)
376 baseinfo
&b
= bases
[i
];
377 putint(p
, max(b
.ammotype
, 0));
378 putint(p
, int(b
.o
.x
*DMF
));
379 putint(p
, int(b
.o
.y
*DMF
));
380 putint(p
, int(b
.o
.z
*DMF
));
385 void updatebase(int i
, const char *owner
, const char *enemy
, int converted
, int ammo
)
387 if(!bases
.inrange(i
)) return;
388 baseinfo
&b
= bases
[i
];
391 if(strcmp(b
.owner
, owner
))
393 conoutf("\f2%s captured %s", owner
, b
.name
);
394 if(!strcmp(owner
, cl
.player1
->team
)) playsound(S_V_BASECAP
);
399 conoutf("\f2%s lost %s", b
.owner
, b
.name
);
400 if(!strcmp(b
.owner
, cl
.player1
->team
)) playsound(S_V_BASELOST
);
402 s_strcpy(b
.owner
, owner
);
403 s_strcpy(b
.enemy
, enemy
);
404 b
.converted
= converted
;
405 if(ammo
>b
.ammo
) playsound(S_ITEMSPAWN
, &b
.o
);
409 void setscore(const char *team
, int total
)
411 findscore(team
).total
= total
;
412 if(total
>=10000) conoutf("team %s captured all bases", team
);
415 int closesttoenemy(const char *team
, bool noattacked
= false, bool farthest
= false)
417 float bestdist
= farthest
? -1e10f
: 1e10f
;
419 int attackers
= INT_MAX
, attacked
= -1;
422 baseinfo
&b
= bases
[i
];
423 if(!b
.owner
[0] || strcmp(b
.owner
, team
)) continue;
424 if(noattacked
&& b
.enemy
[0]) continue;
425 float dist
= disttoenemy(b
);
426 if(farthest
? dist
> bestdist
: dist
< bestdist
)
431 else if(b
.enemy
[0] && b
.enemies
< attackers
)
434 attackers
= b
.enemies
;
437 if(best
< 0) return attacked
;
441 int pickspawn(const char *team
)
443 int gamemode
= cl
.gamemode
, closest
= closesttoenemy(team
, true, m_regencapture
);
444 if(!m_regencapture
&& closest
< 0) closest
= closesttoenemy(team
, false);
445 if(closest
< 0) return -1;
446 baseinfo
&b
= bases
[closest
];
448 float bestdist
= 1e10f
, altdist
= 1e10f
;
449 int best
= -1, alt
= -1;
452 extentity
*e
= cl
.et
.ents
[i
];
453 if(e
->type
!=PLAYERSTART
) continue;
454 float dist
= e
->o
.dist(b
.o
);
462 else if(dist
< altdist
)
468 return rnd(2) ? best
: alt
;
474 struct captureservmode
: capturestate
, servmode
479 captureservmode(fpsserver
&sv
) : servmode(sv
), scoresec(0), notgotbases(false) {}
481 void reset(bool empty
)
483 capturestate::reset();
485 notgotbases
= !empty
;
488 void stealbase(int n
, const char *team
)
490 baseinfo
&b
= bases
[n
];
493 fpsserver::clientinfo
*ci
= sv
.clients
[i
];
494 if(!ci
->spectator
&& ci
->state
.state
==CS_ALIVE
&& ci
->team
[0] && !strcmp(ci
->team
, team
) && insidebase(b
, ci
->state
.o
))
500 void replenishammo(clientinfo
*ci
)
502 int gamemode
= sv
.gamemode
;
503 if(m_noitems
|| notgotbases
|| ci
->state
.state
!=CS_ALIVE
|| !ci
->team
[0]) return;
506 baseinfo
&b
= bases
[i
];
507 if(b
.ammotype
>0 && b
.ammotype
<=I_CARTRIDGES
-I_SHELLS
+1 && insidebase(b
, ci
->state
.o
) && !ci
->state
.hasmaxammo(b
.ammotype
-1+I_SHELLS
) && b
.takeammo(ci
->team
))
510 sendf(ci
->clientnum
, 1, "rii", SV_REPAMMO
, b
.ammotype
);
511 ci
->state
.addammo(b
.ammotype
);
517 void movebases(const char *team
, const vec
&oldpos
, const vec
&newpos
)
519 if(!team
[0] || sv
.minremain
<0) return;
522 baseinfo
&b
= bases
[i
];
523 bool leave
= insidebase(b
, oldpos
),
524 enter
= insidebase(b
, newpos
);
525 if(leave
&& !enter
&& b
.leave(team
)) sendbaseinfo(i
);
526 else if(enter
&& !leave
&& b
.enter(team
)) sendbaseinfo(i
);
527 else if(leave
&& enter
&& b
.steal(team
)) stealbase(i
, team
);
531 void leavebases(const char *team
, const vec
&o
)
533 movebases(team
, o
, vec(-1e10f
, -1e10f
, -1e10f
));
536 void enterbases(const char *team
, const vec
&o
)
538 movebases(team
, vec(-1e10f
, -1e10f
, -1e10f
), o
);
541 void addscore(const char *team
, int n
)
544 score
&cs
= findscore(team
);
546 sendf(-1, 1, "risi", SV_TEAMSCORE
, team
, cs
.total
);
549 void regenowners(baseinfo
&b
, int ticks
)
553 fpsserver::clientinfo
*ci
= sv
.clients
[i
];
554 if(!ci
->spectator
&& ci
->state
.state
==CS_ALIVE
&& ci
->team
[0] && !strcmp(ci
->team
, b
.owner
) && insidebase(b
, ci
->state
.o
))
557 if(ci
->state
.health
< ci
->state
.maxhealth
)
559 ci
->state
.health
= min(ci
->state
.health
+ ticks
*REGENHEALTH
, ci
->state
.maxhealth
);
562 if(ci
->state
.armour
< itemstats
[I_GREENARMOUR
-I_SHELLS
].max
)
564 ci
->state
.armour
= min(ci
->state
.armour
+ ticks
*REGENARMOUR
, itemstats
[I_GREENARMOUR
-I_SHELLS
].max
);
569 int ammotype
= b
.ammotype
-1+I_SHELLS
;
570 if(ammotype
<=I_CARTRIDGES
&& !ci
->state
.hasmaxammo(ammotype
))
572 ci
->state
.addammo(b
.ammotype
, ticks
*REGENAMMO
, 100);
577 sendf(ci
->clientnum
, 1, "ri5", SV_BASEREGEN
, ci
->state
.health
, ci
->state
.armour
, b
.ammotype
, b
.ammotype
>0 ? ci
->state
.ammo
[b
.ammotype
] : 0);
584 if(sv
.minremain
<0) return;
586 int t
= sv
.gamemillis
/1000 - (sv
.gamemillis
-sv
.curtime
)/1000;
588 int gamemode
= sv
.gamemode
;
591 baseinfo
&b
= bases
[i
];
594 if((!b
.owners
|| !b
.enemies
) && b
.occupy(b
.enemy
, (m_noitemsrail
? OCCUPYPOINTS
*2 : OCCUPYPOINTS
)*(b
.enemies
? b
.enemies
: -(1+b
.owners
))*t
)==1) addscore(b
.owner
, CAPTURESCORE
);
600 int score
= b
.capturetime
/SCORESECS
- (b
.capturetime
-t
)/SCORESECS
;
601 if(score
) addscore(b
.owner
, score
);
606 int regen
= b
.capturetime
/REGENSECS
- (b
.capturetime
-t
)/REGENSECS
;
607 if(regen
) regenowners(b
, regen
);
612 int ammo
= b
.capturetime
/AMMOSECS
- (b
.capturetime
-t
)/AMMOSECS
;
613 if(ammo
&& b
.addammo(ammo
)) sendbaseinfo(i
);
619 void sendbaseinfo(int i
)
621 baseinfo
&b
= bases
[i
];
622 sendf(-1, 1, "riissii", SV_BASEINFO
, i
, b
.owner
, b
.enemy
, b
.enemy
[0] ? b
.converted
: 0, b
.owner
[0] ? b
.ammo
: 0);
627 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, ENET_PACKET_FLAG_RELIABLE
);
628 ucharbuf
p(packet
->data
, packet
->dataLength
);
629 initclient(NULL
, p
, false);
630 enet_packet_resize(packet
, p
.length());
631 sendpacket(-1, 1, packet
);
632 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
635 void initclient(clientinfo
*ci
, ucharbuf
&p
, bool connecting
)
637 if(connecting
) loopv(scores
)
639 score
&cs
= scores
[i
];
640 putint(p
, SV_TEAMSCORE
);
641 sendstring(cs
.team
, p
);
647 baseinfo
&b
= bases
[i
];
648 putint(p
, min(max(b
.ammotype
, 1), I_CARTRIDGES
+1));
649 sendstring(b
.owner
, p
);
650 sendstring(b
.enemy
, p
);
651 putint(p
, b
.converted
);
659 const char *lastteam
= NULL
;
663 baseinfo
&b
= bases
[i
];
666 if(!lastteam
) lastteam
= b
.owner
;
667 else if(strcmp(lastteam
, b
.owner
))
680 if(!lastteam
) return;
681 findscore(lastteam
).total
= 10000;
682 sendf(-1, 1, "risi", SV_TEAMSCORE
, lastteam
, 10000);
683 sv
.startintermission();
686 void entergame(clientinfo
*ci
)
688 if(notgotbases
|| ci
->state
.state
!=CS_ALIVE
) return;
689 enterbases(ci
->team
, ci
->state
.o
);
692 void spawned(clientinfo
*ci
)
694 if(notgotbases
) return;
695 enterbases(ci
->team
, ci
->state
.o
);
698 void leavegame(clientinfo
*ci
, bool disconnecting
= false)
700 if(notgotbases
|| ci
->state
.state
!=CS_ALIVE
) return;
701 leavebases(ci
->team
, ci
->state
.o
);
704 void died(clientinfo
*ci
, clientinfo
*actor
)
706 if(notgotbases
) return;
707 leavebases(ci
->team
, ci
->state
.o
);
710 void moved(clientinfo
*ci
, const vec
&oldpos
, const vec
&newpos
)
712 if(notgotbases
) return;
713 movebases(ci
->team
, oldpos
, newpos
);
716 void changeteam(clientinfo
*ci
, const char *oldteam
, const char *newteam
)
718 if(notgotbases
) return;
719 leavebases(oldteam
, ci
->state
.o
);
720 enterbases(newteam
, ci
->state
.o
);
723 void parsebases(ucharbuf
&p
)
726 while((ammotype
= getint(p
))>=0)
732 if(notgotbases
) addbase(ammotype
>=GUN_SG
&& ammotype
<=GUN_PISTOL
? ammotype
: 0, o
);
738 loopv(sv
.clients
) if(sv
.clients
[i
]->state
.state
==CS_ALIVE
) entergame(sv
.clients
[i
]);