Initial sauer
[SauerbratenRemote.git] / src / fpsgame / capture.h
bloba9c30cff82dfaa0249cba85add1c98d701af98f2
1 // capture.h: client and server state for capture gamemode
3 struct capturestate
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;
20 struct baseinfo
22 vec o;
23 string owner, enemy;
24 #ifndef CAPTURESERV
25 string name, info;
26 extentity *ent;
27 #endif
28 int ammotype, ammo, owners, enemies, converted, capturetime;
30 baseinfo() { reset(); }
32 void noenemy()
34 enemy[0] = '\0';
35 enemies = 0;
36 converted = 0;
39 void reset()
41 noenemy();
42 owner[0] = '\0';
43 capturetime = -1;
44 ammotype = 0;
45 ammo = 0;
46 owners = 0;
49 bool enter(const char *team)
51 if(!strcmp(owner, team))
53 owners++;
54 return false;
56 if(!enemies)
58 if(strcmp(enemy, team))
60 converted = 0;
61 s_strcpy(enemy, team);
63 enemies++;
64 return true;
66 else if(strcmp(enemy, team)) return false;
67 else enemies++;
68 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))
80 owners--;
81 return false;
83 if(strcmp(enemy, team)) return false;
84 enemies--;
85 return !enemies;
88 int occupy(const char *team, int units)
90 if(strcmp(enemy, team)) return -1;
91 converted += units;
92 if(units<0)
94 if(converted<=0) noenemy();
95 return -1;
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; }
102 bool addammo(int i)
104 if(ammo>=MAXAMMO) return false;
105 ammo = min(ammo+i, MAXAMMO);
106 return true;
109 bool takeammo(const char *team)
111 if(strcmp(owner, team) || ammo<=0) return false;
112 ammo--;
113 return true;
117 vector<baseinfo> bases;
119 struct score
121 string team;
122 int total;
125 vector<score> scores;
127 int captures;
129 capturestate() : captures(0) {}
131 void reset()
133 bases.setsize(0);
134 scores.setsize(0);
135 captures = 0;
138 score &findscore(const char *team)
140 loopv(scores)
142 score &cs = scores[i];
143 if(!strcmp(cs.team, team)) return cs;
145 score &cs = scores.add();
146 s_strcpy(cs.team, team);
147 cs.total = 0;
148 return cs;
151 void addbase(int ammotype, const vec &o)
153 baseinfo &b = bases.add();
154 b.ammotype = ammotype ? ammotype : rnd(5)+1;
155 b.o = o;
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;
166 b.ammo = ammo;
169 bool hasbases(const char *team)
171 loopv(bases)
173 baseinfo &b = bases[i];
174 if(b.owner[0] && !strcmp(b.owner, team)) return true;
176 return false;
179 float disttoenemy(baseinfo &b)
181 float dist = 1e10f;
182 loopv(bases)
184 baseinfo &e = bases[i];
185 if(e.owner[0] && strcmp(b.owner, e.owner))
186 dist = min(dist, b.o.dist(e.o));
188 return dist;
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;
198 #ifndef CAPTURESERV
200 struct captureclient : capturestate
202 fpsclient &cl;
203 float radarscale;
205 captureclient(fpsclient &cl) : cl(cl), radarscale(0)
207 CCOMMAND(repammo, "", (captureclient *self), self->replenishammo());
210 void replenishammo()
212 int gamemode = cl.gamemode;
213 if(m_noitems) return;
214 loopv(bases)
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)
224 type += I_SHELLS-1;
225 if(type<I_SHELLS || type>I_CARTRIDGES) return;
226 cl.et.repammo(cl.player1, type);
229 void renderbases()
231 int gamemode = cl.gamemode;
232 loopv(bases)
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));
240 vec p(b.o);
241 p.x += 10*cosf(angle);
242 p.y += 10*sinf(angle);
243 p.z += 4;
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;
247 if(b.owner[0])
249 bool isowner = !strcmp(b.owner, cl.player1->team);
250 if(b.enemy[0])
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; }
257 else if(b.enemy[0])
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';
264 vec above(b.o);
265 abovemodel(above, flagname);
266 above.z += 2.0f;
267 particle_text(above, b.info, ttype, 1);
268 if(mtype>=0)
270 above.z += 3.0f;
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)]);
288 glBegin(GL_QUADS);
289 float scale = radarscale<=0 || radarscale>cl.maxradarscale() ? cl.maxradarscale() : radarscale;
290 loopv(bases)
292 baseinfo &b = bases[i];
293 if(skipenemy && b.enemy[0]) continue;
294 switch(type)
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;
301 vec dir(b.o);
302 dir.sub(cl.player1->o);
303 dir.z = 0.0f;
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);
309 glEnd();
312 int respawnwait()
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)
321 glEnable(GL_BLEND);
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;
324 glColor3f(1, 1, 1);
325 settexture("data/radar.png");
326 glBegin(GL_QUADS);
327 drawradar(float(x), float(y), float(s));
328 glEnd();
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();
337 if(wait>=0)
339 glPushMatrix();
340 glLoadIdentity();
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);
343 glPopMatrix();
346 glDisable(GL_BLEND);
349 void setupbases()
351 reset();
352 loopv(cl.et.ents)
354 extentity *e = cl.et.ents[i];
355 if(e->type!=BASE) continue;
356 baseinfo &b = bases.add();
357 b.o = e->o;
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());
362 b.ent = e;
364 vec center(0, 0, 0);
365 loopv(bases) center.add(bases[i].o);
366 center.div(bases.length());
367 radarscale = 0;
368 loopv(bases) radarscale = max(radarscale, 2*center.dist(bases[i].o));
371 void sendbases(ucharbuf &p)
373 putint(p, SV_BASES);
374 loopv(bases)
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));
382 putint(p, -1);
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];
389 if(owner[0])
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);
397 else if(b.owner[0])
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);
406 b.ammo = ammo;
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;
418 int best = -1;
419 int attackers = INT_MAX, attacked = -1;
420 loopv(bases)
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)
428 best = i;
429 bestdist = dist;
431 else if(b.enemy[0] && b.enemies < attackers)
433 attacked = i;
434 attackers = b.enemies;
437 if(best < 0) return attacked;
438 return best;
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;
450 loopv(cl.et.ents)
452 extentity *e = cl.et.ents[i];
453 if(e->type!=PLAYERSTART) continue;
454 float dist = e->o.dist(b.o);
455 if(dist < bestdist)
457 alt = best;
458 altdist = bestdist;
459 best = i;
460 bestdist = dist;
462 else if(dist < altdist)
464 alt = i;
465 altdist = dist;
468 return rnd(2) ? best : alt;
472 #else
474 struct captureservmode : capturestate, servmode
476 int scoresec;
477 bool notgotbases;
479 captureservmode(fpsserver &sv) : servmode(sv), scoresec(0), notgotbases(false) {}
481 void reset(bool empty)
483 capturestate::reset();
484 scoresec = 0;
485 notgotbases = !empty;
488 void stealbase(int n, const char *team)
490 baseinfo &b = bases[n];
491 loopv(sv.clients)
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))
495 b.enter(ci->team);
497 sendbaseinfo(n);
500 void replenishammo(clientinfo *ci)
502 int gamemode = sv.gamemode;
503 if(m_noitems || notgotbases || ci->state.state!=CS_ALIVE || !ci->team[0]) return;
504 loopv(bases)
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))
509 sendbaseinfo(i);
510 sendf(ci->clientnum, 1, "rii", SV_REPAMMO, b.ammotype);
511 ci->state.addammo(b.ammotype);
512 break;
517 void movebases(const char *team, const vec &oldpos, const vec &newpos)
519 if(!team[0] || sv.minremain<0) return;
520 loopv(bases)
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)
543 if(!n) return;
544 score &cs = findscore(team);
545 cs.total += n;
546 sendf(-1, 1, "risi", SV_TEAMSCORE, team, cs.total);
549 void regenowners(baseinfo &b, int ticks)
551 loopv(sv.clients)
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))
556 bool notify = false;
557 if(ci->state.health < ci->state.maxhealth)
559 ci->state.health = min(ci->state.health + ticks*REGENHEALTH, ci->state.maxhealth);
560 notify = true;
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);
565 notify = true;
567 if(b.ammotype>0)
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);
573 notify = true;
576 if(notify)
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);
582 void update()
584 if(sv.minremain<0) return;
585 endcheck();
586 int t = sv.gamemillis/1000 - (sv.gamemillis-sv.curtime)/1000;
587 if(t<1) return;
588 int gamemode = sv.gamemode;
589 loopv(bases)
591 baseinfo &b = bases[i];
592 if(b.enemy[0])
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);
595 sendbaseinfo(i);
597 else if(b.owner[0])
599 b.capturetime += t;
600 int score = b.capturetime/SCORESECS - (b.capturetime-t)/SCORESECS;
601 if(score) addscore(b.owner, score);
602 if(m_noitems)
604 if(!m_noitemsrail)
606 int regen = b.capturetime/REGENSECS - (b.capturetime-t)/REGENSECS;
607 if(regen) regenowners(b, regen);
610 else
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);
625 void sendbases()
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);
642 putint(p, cs.total);
644 putint(p, SV_BASES);
645 loopv(bases)
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);
652 putint(p, b.ammo);
654 putint(p, -1);
657 void endcheck()
659 const char *lastteam = NULL;
661 loopv(bases)
663 baseinfo &b = bases[i];
664 if(b.owner[0])
666 if(!lastteam) lastteam = b.owner;
667 else if(strcmp(lastteam, b.owner))
669 lastteam = false;
670 break;
673 else
675 lastteam = false;
676 break;
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)
725 int ammotype;
726 while((ammotype = getint(p))>=0)
728 vec o;
729 o.x = getint(p)/DMF;
730 o.y = getint(p)/DMF;
731 o.z = getint(p)/DMF;
732 if(notgotbases) addbase(ammotype>=GUN_SG && ammotype<=GUN_PISTOL ? ammotype : 0, o);
734 if(notgotbases)
736 notgotbases = false;
737 sendbases();
738 loopv(sv.clients) if(sv.clients[i]->state.state==CS_ALIVE) entergame(sv.clients[i]);
743 #endif