1 #include "game.h"
2 namespace capture
3 {
4     capturestate st;
5 
6     ICOMMAND(0, getcapturedelay, "i", (int *n), intret(capturedelay));
7     ICOMMAND(0, getcapturestore, "i", (int *n), intret(capturestore));
8 
9     ICOMMAND(0, getcapturenum, "", (), intret(st.flags.length()));
10     ICOMMAND(0, getcaptureteam, "i", (int *n), intret(st.flags.inrange(*n) ? st.flags[*n].team : -1));
11     ICOMMAND(0, getcapturedroptime, "i", (int *n), intret(st.flags.inrange(*n) ? st.flags[*n].droptime : -1));
12     ICOMMAND(0, getcapturetaketime, "i", (int *n), intret(st.flags.inrange(*n) ? st.flags[*n].taketime : -1));
13     ICOMMAND(0, getcapturedisptime, "i", (int *n), intret(st.flags.inrange(*n) ? st.flags[*n].displaytime : -1));
14     ICOMMAND(0, getcaptureowner, "i", (int *n), intret(st.flags.inrange(*n) && st.flags[*n].owner ? st.flags[*n].owner->clientnum : -1));
15     ICOMMAND(0, getcapturelastowner, "i", (int *n), intret(st.flags.inrange(*n) && st.flags[*n].lastowner ? st.flags[*n].lastowner->clientnum : -1));
16 
radarallow(int id,int render,vec & dir,float & dist,bool justtest=false)17     bool radarallow(int id, int render, vec &dir, float &dist, bool justtest = false)
18     {
19         if(!st.flags.inrange(id) || (m_hard(game::gamemode, game::mutators) && !G(radarhardaffinity))) return false;
20         if(justtest) return true;
21         dir = vec(render > 0 ? st.flags[id].spawnloc : st.flags[id].pos(render < 0)).sub(camera1->o);
22         dist = dir.magnitude();
23         if(st.flags[id].owner != game::focus && hud::radarlimited(dist)) return false;
24         return true;
25     }
26 
27     ICOMMAND(0, getcaptureradarallow, "ibi", (int *n, int *v, int *q),
28     {
29         vec dir(0, 0, 0);
30         float dist = -1;
31         intret(radarallow(*n, *v, dir, dist, *q != 0) ? 1 : 0);
32     });
33     ICOMMAND(0, getcaptureradardist, "ib", (int *n, int *v),
34     {
35         vec dir(0, 0, 0);
36         float dist = -1;
37         if(!radarallow(*n, *v, dir, dist)) return;
38         floatret(dist);
39     });
40     ICOMMAND(0, getcaptureradardir, "ib", (int *n, int *v),
41     {
42         vec dir(0, 0, 0);
43         float dist = -1;
44         if(!radarallow(*n, *v, dir, dist)) return;
45         dir.rotate_around_z(-camera1->yaw*RAD).normalize();
46         floatret(-atan2(dir.x, dir.y)/RAD);
47     });
48 
49     #define LOOPCAPTURE(name,op) \
50         ICOMMAND(0, loopcapture##name, "iire", (int *count, int *skip, ident *id, uint *body), \
51         { \
52             if(!m_capture(game::gamemode)) return; \
53             loopstart(id, stack); \
54             op(st.flags, *count, *skip) \
55             { \
56                 loopiter(id, stack, i); \
57                 execute(body); \
58             } \
59             loopend(id, stack); \
60         });
61     LOOPCAPTURE(,loopcsv);
62     LOOPCAPTURE(rev,loopcsvrev);
63 
64     #define LOOPCAPTUREIF(name,op) \
65         ICOMMAND(0, loopcapture##name##if, "iiree", (int *count, int *skip, ident *id, uint *cond, uint *body), \
66         { \
67             if(!m_capture(game::gamemode)) return; \
68             loopstart(id, stack); \
69             op(st.flags, *count, *skip) \
70             { \
71                 loopiter(id, stack, i); \
72                 if(executebool(cond)) execute(body); \
73             } \
74             loopend(id, stack); \
75         });
76     LOOPCAPTUREIF(,loopcsv);
77     LOOPCAPTUREIF(rev,loopcsvrev);
78 
carryaffinity(gameent * d)79     int carryaffinity(gameent *d)
80     {
81         int n = 0;
82         loopv(st.flags) if(st.flags[i].owner == d) n++;
83         return n;
84     }
85 
dropaffinity(gameent * d)86     bool dropaffinity(gameent *d)
87     {
88         if(!carryaffinity(d) || !d->action[AC_AFFINITY]) return false;
89         vec o = d->feetpos(capturedropheight), inertia = vec(d->vel).add(d->falling);
90         client::addmsg(N_DROPAFFIN, "ri8", d->clientnum, -1, int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(inertia.x*DMF), int(inertia.y*DMF), int(inertia.z*DMF));
91         d->action[AC_AFFINITY] = false;
92         return true;
93     }
94 
canpickup(gameent * d,int n,bool check=false)95     bool canpickup(gameent *d, int n, bool check = false)
96     {
97         if(!st.flags.inrange(n) || !(AA(d->actortype, abilities)&(1<<A_A_AFFINITY))) return false;
98         capturestate::flag &f = st.flags[n];
99         if(f.owner) return false;
100         if(f.team == d->team)
101         {
102             if(m_ctf_defend(game::gamemode, game::mutators)) return false;
103             if(!f.droptime)
104             {
105                 if(m_ctf_quick(game::gamemode, game::mutators)) return false;
106                 if(!check && !d->action[AC_AFFINITY]) return false;
107             }
108         }
109         if(f.lastowner == d && f.droptime && lastmillis-f.droptime <= capturepickupdelay) return false;
110         if((f.pos()).dist(d->feetpos()) > enttype[AFFINITY].radius*2/3) return false;
111         return true;
112     }
113 
preload()114     void preload()
115     {
116         preloadmodel("props/point");
117         preloadmodel("props/flag");
118     }
119 
drawonscreen(int w,int h,float blend)120     void drawonscreen(int w, int h, float blend)
121     {
122     }
123 
buildflagstr(vector<int> & f,bool named=false)124     char *buildflagstr(vector<int> &f, bool named = false)
125     {
126         static string s; s[0] = '\0';
127         loopv(f)
128         {
129             defformatstring(d, "\fs%s\f[%d]\f(%s)%s\fS", i && named ? (i == f.length()-1 ? " & " : ", ") : "", TEAM(st.flags[f[i]].team, colour), hud::flagtex, named ? TEAM(st.flags[f[i]].team, name) : "");
130             concatstring(s, d);
131         }
132         return s;
133     }
134 
drawnotices(int w,int h,int & tx,int & ty,int tr,int tg,int tb,float blend)135     void drawnotices(int w, int h, int &tx, int &ty, int tr, int tg, int tb, float blend)
136     {
137         if(game::focus->state != CS_ALIVE || hud::shownotices < 2) return;
138         if(game::focus->lastbuff && hud::shownotices >= 3)
139         {
140             pushfont("reduced");
141             if(m_regen(game::gamemode, game::mutators) && captureregenbuff && captureregenextra)
142             {
143                 if(game::damageinteger)
144                     ty += draw_textf("Buffing: \fs\fo%d%%\fS damage, \fs\fg%d%%\fS shield, +\fs\fy%d\fS regen", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, int(bomberbuffdamage*100), int(bomberbuffshield*100), int(ceilf(bomberregenextra/game::damagedivisor)));
145                 else ty += draw_textf("Buffing: \fs\fo%d%%\fS damage, \fs\fg%d%%\fS shield, +\fs\fy%1.f\fS regen", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, int(bomberbuffdamage*100), int(bomberbuffshield*100), bomberregenextra/game::damagedivisor);
146             }
147             else ty += draw_textf("Buffing: \fs\fo%d%%\fS damage, \fs\fg%d%%\fS shield", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, int(capturebuffdamage*100), int(capturebuffshield*100));
148             popfont();
149         }
150         bool ownflag = false;
151         static vector<int> pickup, hasflags, taken, droppedflags;
152         pickup.setsize(0); hasflags.setsize(0); taken.setsize(0); droppedflags.setsize(0);
153         loopv(st.flags)
154         {
155             capturestate::flag &f = st.flags[i];
156             if(f.owner == game::focus)
157             {
158                 hasflags.add(i);
159                 if(f.team == game::focus->team) ownflag = true;
160             }
161             if(canpickup(game::focus, i, true)) pickup.add(i);
162             if(f.team == game::focus->team)
163             {
164                 if(f.owner && f.owner->team != game::focus->team) taken.add(i);
165                 else if(f.droptime) droppedflags.add(i);
166             }
167         }
168         if(!hasflags.empty())
169         {
170             if(capturebuffing&(ownflag ? 8 : 32))
171             {
172                 pushfont("reduced");
173                 if(capturebuffarea > 0) ty += draw_textf("Buffing team-mates within \fs\fy%.2f\fom\fS", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, capturebuffarea/8.f);
174                 else ty += draw_textf("Buffing \fs\fyALL\fS team-mates", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED);
175                 popfont();
176             }
177         }
178         if(!pickup.empty())
179         {
180             pushfont("emphasis");
181             char *str = buildflagstr(pickup, pickup.length() <= 3);
182             ty += draw_textf("Nearby: %s", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, str);
183             popfont();
184         }
185         if(game::focus == game::player1 && (!hasflags.empty() || !pickup.empty()))
186         {
187             pushfont("reduced");
188             ty += draw_textf("Press \fs\fw\f{=affinity}\fS to %s", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, !hasflags.empty() ? "drop flags" : "pick up flags");
189             popfont();
190         }
191         if(!taken.empty())
192         {
193             pushfont("default");
194             char *str = buildflagstr(taken, taken.length() <= 3);
195             ty += draw_textf("%s taken: %s", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, taken.length() == 1 ? "Flag" : "Flags", str);
196             popfont();
197         }
198         if(!droppedflags.empty())
199         {
200             pushfont("default");
201             char *str = buildflagstr(droppedflags, droppedflags.length() <= 3);
202             ty += draw_textf("%s dropped: %s", tx, ty, int(FONTW*hud::noticepadx), int(FONTH*hud::noticepady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, droppedflags.length() == 1 ? "Flag" : "Flags", str);
203             popfont();
204         }
205     }
206 
drawevents(int w,int h,int & tx,int & ty,int tr,int tg,int tb,float blend)207     void drawevents(int w, int h, int &tx, int &ty, int tr, int tg, int tb, float blend)
208     {
209         if(game::focus->state != CS_ALIVE || hud::showevents < 2) return;
210         static vector<int> hasflags;
211         hasflags.setsize(0);
212         loopv(st.flags)
213         {
214             capturestate::flag &f = st.flags[i];
215             if(f.owner == game::focus) hasflags.add(i);
216         }
217         if(!hasflags.empty())
218         {
219             char *str = buildflagstr(hasflags, hasflags.length() <= 3);
220             ty -= draw_textf("You are holding the %s %s", tx, ty, int(FONTW*hud::eventpadx), int(FONTH*hud::eventpady), tr, tg, tb, int(255*blend), TEXT_CENTERED, -1, -1, 1, str, hasflags.length() == 1 ? "flag" : "flags")+FONTH/4;
221         }
222     }
223 
checkcams(vector<cament * > & cameras)224     void checkcams(vector<cament *> &cameras)
225     {
226         loopv(st.flags) // flags/bases
227         {
228             capturestate::flag &f = st.flags[i];
229             cament *c = cameras.add(new cament(cameras.length(), cament::AFFINITY, i));
230             c->o = f.pos(true);
231             c->o.z += enttype[AFFINITY].radius*2/3;
232             c->player = f.owner;
233         }
234     }
235 
updatecam(cament * c)236     void updatecam(cament *c)
237     {
238         switch(c->type)
239         {
240             case cament::AFFINITY:
241             {
242                 if(!st.flags.inrange(c->id)) break;
243                 capturestate::flag &f = st.flags[c->id];
244                 c->o = f.pos(true);
245                 c->o.z += enttype[AFFINITY].radius*2/3;
246                 c->player = f.owner;
247                 break;
248             }
249             default: break;
250         }
251     }
252 
render()253     void render()
254     {
255         static vector<int> numflags, iterflags; // dropped/owned
256         loopv(numflags) numflags[i] = iterflags[i] = 0;
257         loopv(st.flags)
258         {
259             capturestate::flag &f = st.flags[i];
260             if(!f.owner) continue;
261             while(f.owner->clientnum >= numflags.length())
262             {
263                 numflags.add(0);
264                 iterflags.add(0);
265             }
266             numflags[f.owner->clientnum]++;
267         }
268         loopv(st.flags) // flags/bases
269         {
270             capturestate::flag &f = st.flags[i];
271             modelstate mdl, basemdl;
272             vec pos = f.pos(true);
273             float wait = f.droptime ? clamp(f.dropleft(lastmillis, capturestore)/float(capturedelay), 0.f, 1.f) : ((m_ctf_protect(game::gamemode, game::mutators) && f.taketime && f.owner && f.owner->team != f.team) ? clamp((lastmillis-f.taketime)/float(captureprotectdelay), 0.f, 1.f) : 0.f),
274                   blend = 1.f;
275             vec effect = vec::fromcolor(TEAM(f.team, colour));
276             int colour = effect.tohexcolor();
277             if(!f.owner && (!f.droptime || m_ctf_defend(game::gamemode, game::mutators)) && f.team == game::focus->team)
278                 blend *= camera1->o.distrange(pos, enttype[AFFINITY].radius, enttype[AFFINITY].radius/8);
279             if(wait > 0.5f)
280             {
281                 int delay = wait > 0.7f ? (wait > 0.85f ? 150 : 300) : 600, millis = lastmillis%(delay*2);
282                 float amt = (millis <= delay ? millis/float(delay) : 1.f-((millis-delay)/float(delay)));
283                 flashcolour(effect.r, effect.g, effect.b, 0.65f, 0.65f, 0.65f, amt);
284             }
285             basemdl.material[0] = mdl.material[0] = bvec::fromcolor(effect);
286             mdl.anim = ANIM_MAPMODEL|ANIM_LOOP;
287             mdl.flags = MDL_CULL_VFC|MDL_CULL_OCCLUDED;
288             if(!f.owner && !f.droptime)
289             {
290                 vec flagpos = pos;
291                 mdl.o = flagpos;
292                 mdl.color = vec4(1, 1, 1, blend);
293                 rendermodel("props/flag", mdl);
294                 flagpos.z += enttype[AFFINITY].radius/3;
295                 if(game::affinityhint)
296                     part_create(PART_HINT_SOFT, 1, flagpos, effect.tohexcolor(), enttype[AFFINITY].radius/2*game::affinityhintsize, blend*game::affinityhintblend*camera1->o.distrange(flagpos, game::affinityhintfadeat, game::affinityhintfadecut));
297             }
298             else if(!f.owner || f.owner != game::focus || game::thirdpersonview(true))
299             {
300                 vec flagpos = pos;
301                 if(f.owner)
302                 {
303                     if(f.owner == game::focus && game::thirdpersonview(true))
304                         blend *= f.owner != game::player1 ? game::affinityfollowblend : game::affinitythirdblend;
305                     mdl.yaw = f.owner->yaw-45.f+(90/float(numflags[f.owner->clientnum]+1)*(iterflags[f.owner->clientnum]+1));
306                 }
307                 else
308                 {
309                     mdl.yaw = ((lastmillis/8)+(360/st.flags.length()*i))%360;
310                     if(f.proj) flagpos.z -= f.proj->height;
311                 }
312                 while(mdl.yaw >= 360.f) mdl.yaw -= 360.f;
313                 mdl.o = flagpos;
314                 mdl.color = vec4(1, 1, 1, blend);
315                 rendermodel("props/flag", mdl);
316                 flagpos.z += enttype[AFFINITY].radius/3;
317                 if(game::affinityhint)
318                     part_create(PART_HINT_SOFT, 1, flagpos, effect.tohexcolor(), enttype[AFFINITY].radius/2*game::affinityhintsize, blend*game::affinityhintblend*camera1->o.distrange(flagpos, game::affinityhintfadeat, game::affinityhintfadecut));
319                 flagpos.z += enttype[AFFINITY].radius/2;
320                 if(f.owner)
321                 {
322                     flagpos.z += iterflags[f.owner->clientnum]*2;
323                     iterflags[f.owner->clientnum]++;
324                 }
325                 if(gs_playing(game::gamestate) && (f.droptime || (m_ctf_protect(game::gamemode, game::mutators) && f.taketime && f.owner && f.owner->team != f.team)))
326                 {
327                     float wait = f.droptime ? clamp(f.dropleft(lastmillis, capturestore)/float(capturedelay), 0.f, 1.f) : clamp((lastmillis-f.taketime)/float(captureprotectdelay), 0.f, 1.f);
328                     part_icon(flagpos, textureload(hud::progringtex, 3), 5, blend, 0, 0, 1, colour, (lastmillis%1000)/1000.f, 0.1f);
329                     part_icon(flagpos, textureload(hud::progresstex, 3), 5, 0.25f*blend, 0, 0, 1, colour);
330                     part_icon(flagpos, textureload(hud::progresstex, 3), 5, blend, 0, 0, 1, colour, 0, wait);
331                 }
332             }
333             basemdl.anim = ANIM_MAPMODEL|ANIM_LOOP;
334             basemdl.flags = MDL_CULL_VFC|MDL_CULL_OCCLUDED;
335             basemdl.o = f.render;
336             rendermodel("props/point", basemdl);
337             vec above = f.spawnloc;
338             above.z += !f.owner && !f.droptime ? enttype[AFFINITY].radius/2 + 4 : 3;
339             blend = camera1->o.distrange(above, enttype[AFFINITY].radius, enttype[AFFINITY].radius/8);
340             defformatstring(info, "<super>%s Base", TEAM(f.team, name));
341             part_textcopy(above, info, PART_TEXT, 1, TEAM(f.team, colour), 2, blend);
342             above.z += 5;
343             if(gs_playing(game::gamestate) && (f.droptime || (m_ctf_protect(game::gamemode, game::mutators) && f.taketime && f.owner && f.owner->team != f.team)))
344             {
345                 part_icon(above, textureload(hud::progringtex, 3), 5, blend, 0, 0, 1, colour, (lastmillis%1000)/1000.f, 0.1f);
346                 part_icon(above, textureload(hud::progresstex, 3), 5, 0.25f*blend, 0, 0, 1, colour);
347                 part_icon(above, textureload(hud::progresstex, 3), 5, blend, 0, 0, 1, colour, 0, wait);
348                 above.z += 8;
349             }
350             if(f.owner) part_icon(above, textureload(hud::flagtakentex, 3), 4, blend, 0, 0, 1, TEAM(f.owner->team, colour));
351             else if(f.droptime) part_icon(above, textureload(hud::flagdroptex, 3), 4, blend, 0, 0, 1, colourcyan);
352             else part_icon(above, textureload(hud::teamtexname(f.team), 3), 4, blend, 0, 0, 1, TEAM(f.team, colour));
353         }
354     }
355 
adddynlights()356     void adddynlights()
357     {
358         loopv(st.flags)
359         {
360             capturestate::flag &f = st.flags[i];
361             if(f.owner || f.droptime)
362                 adddynlight(vec(f.spawnloc).add(vec(0, 0, enttype[AFFINITY].radius/2)), enttype[AFFINITY].radius, vec::fromcolor(TEAM(f.team, colour)), 0, 0);
363             adddynlight(vec(f.pos(true)).add(vec(0, 0, enttype[AFFINITY].radius/2)), enttype[AFFINITY].radius, vec::fromcolor(TEAM(f.team, colour)), 0, 0);
364         }
365     }
366 
reset()367     void reset()
368     {
369         st.reset();
370     }
371 
setup()372     void setup()
373     {
374         loopv(entities::ents) if(entities::ents[i]->type == AFFINITY)
375         {
376             gameentity &e = *(gameentity *)entities::ents[i];
377             if(!checkmapvariant(e.attrs[enttype[e.type].mvattr])) continue;
378             if(!m_check(e.attrs[3], e.attrs[4], game::gamemode, game::mutators)) continue;
379             if(!isteam(game::gamemode, game::mutators, e.attrs[0], T_FIRST)) continue;
380             st.addaffinity(e.o, e.attrs[0], e.attrs[1], e.attrs[2]);
381             if(st.flags.length() >= MAXPARAMS) break;
382         }
383     }
384 
sendaffinity(packetbuf & p)385     void sendaffinity(packetbuf &p)
386     {
387         putint(p, N_INITAFFIN);
388         putint(p, st.flags.length());
389         loopv(st.flags)
390         {
391             capturestate::flag &f = st.flags[i];
392             putint(p, f.team);
393             putint(p, f.yaw);
394             putint(p, f.pitch);
395             loopj(3) putint(p, int(f.spawnloc[j]*DMF));
396         }
397     }
398 
setscore(int team,int total)399     void setscore(int team, int total)
400     {
401         hud::teamscore(team).total = total;
402     }
403 
parseaffinity(ucharbuf & p)404     void parseaffinity(ucharbuf &p)
405     {
406         int numflags = getint(p);
407         if(numflags < 0) return;
408         while(st.flags.length() > numflags) st.flags.pop();
409         loopi(numflags)
410         {
411             int team = getint(p), yaw = getint(p), pitch = getint(p), owner = getint(p), dropped = 0, dropoffset = -1;
412             vec spawnloc(0, 0, 0), droploc(0, 0, 0), inertia(0, 0, 0);
413             loopj(3) spawnloc[j] = getint(p)/DMF;
414             if(owner < 0)
415             {
416                 dropped = getint(p);
417                 if(dropped)
418                 {
419                     dropoffset = getint(p);
420                     loopj(3) droploc[j] = getint(p)/DMF;
421                     loopj(3) inertia[j] = getint(p)/DMF;
422                 }
423             }
424             if(p.overread()) break;
425             if(i >= MAXPARAMS) continue;
426             while(!st.flags.inrange(i)) st.flags.add();
427             capturestate::flag &f = st.flags[i];
428             f.reset();
429             f.team = team;
430             f.yaw = yaw;
431             f.pitch = pitch;
432             f.setposition(spawnloc);
433             if(owner >= 0) st.takeaffinity(i, game::newclient(owner), lastmillis);
434             else if(dropped) st.dropaffinity(i, droploc, inertia, lastmillis, dropoffset);
435         }
436     }
437 
dropaffinity(gameent * d,int i,const vec & droploc,const vec & inertia,int offset)438     void dropaffinity(gameent *d, int i, const vec &droploc, const vec &inertia, int offset)
439     {
440         if(!st.flags.inrange(i)) return;
441         capturestate::flag &f = st.flags[i];
442         game::announcef(S_V_FLAGDROP, CON_EVENT, d, true, "\fa%s dropped the the %s flag", game::colourname(d), game::colourteam(f.team, "flagtex"));
443         st.dropaffinity(i, droploc, inertia, lastmillis, offset);
444     }
445 
removeplayer(gameent * d)446     void removeplayer(gameent *d)
447     {
448         loopv(st.flags) if(st.flags[i].owner == d)
449         {
450             capturestate::flag &f = st.flags[i];
451             st.dropaffinity(i, f.owner->feetpos(capturedropheight), f.owner->vel, lastmillis);
452         }
453     }
454 
affinityeffect(int i,int team,const vec & from,const vec & to,int effect,const char * str)455     void affinityeffect(int i, int team, const vec &from, const vec &to, int effect, const char *str)
456     {
457         if(from.x >= 0)
458         {
459             if(effect&1 && game::aboveheadaffinity)
460             {
461                 defformatstring(text, "<huge>\fzuw%s", str);
462                 part_textcopy(vec(from).add(vec(0, 0, enttype[AFFINITY].radius)), text, PART_TEXT, game::eventiconfade, TEAM(team, colour), 3, 1, -10);
463             }
464             if(game::dynlighteffects) adddynlight(vec(from).add(vec(0, 0, enttype[AFFINITY].radius)), enttype[AFFINITY].radius*2, vec::fromcolor(TEAM(team, colour)).mul(2.f), 500, 250);
465         }
466         if(to.x >= 0)
467         {
468             if(effect&2 && game::aboveheadaffinity)
469             {
470                 defformatstring(text, "<huge>\fzuw%s",str);
471                 part_textcopy(vec(to).add(vec(0, 0, enttype[AFFINITY].radius)), text, PART_TEXT, game::eventiconfade, TEAM(team, colour), 3, 1, -10);
472             }
473             if(game::dynlighteffects) adddynlight(vec(to).add(vec(0, 0, enttype[AFFINITY].radius)), enttype[AFFINITY].radius*2, vec::fromcolor(TEAM(team, colour)).mul(2.f), 500, 250);
474         }
475         if(from.x >= 0 && to.x >= 0 && from != to) part_trail(PART_SPARK, 500, from, to, TEAM(team, colour), 1, 1, -10);
476     }
477 
returnaffinity(gameent * d,int i)478     void returnaffinity(gameent *d, int i)
479     {
480         if(!st.flags.inrange(i)) return;
481         capturestate::flag &f = st.flags[i];
482         affinityeffect(i, d->team, d->feetpos(), f.spawnloc, 3, "RETURNED");
483         game::spawneffect(PART_SPARK, vec(f.spawnloc).add(vec(0, 0, enttype[AFFINITY].radius*0.45f)), enttype[AFFINITY].radius*0.25f, TEAM(f.team, colour), 1);
484         game::spawneffect(PART_SPARK, vec(f.spawnloc).add(vec(0, 0, enttype[AFFINITY].radius*0.45f)), enttype[AFFINITY].radius*0.25f, colourwhite, 1);
485         game::announcef(S_V_FLAGRETURN, CON_EVENT, d, true, "\fa%s returned the %s flag (time taken: \fs\fc%s\fS)", game::colourname(d), game::colourteam(f.team, "flagtex"), timestr(m_ctf_quick(game::gamemode, game::mutators) ? f.dropleft(lastmillis, capturestore) : lastmillis-f.taketime, 1));
486         st.returnaffinity(i, lastmillis);
487     }
488 
resetaffinity(int i,int value,const vec & pos)489     void resetaffinity(int i, int value, const vec &pos)
490     {
491         if(!st.flags.inrange(i)) return;
492         capturestate::flag &f = st.flags[i];
493         if(value > 0)
494         {
495             affinityeffect(i, T_NEUTRAL, f.droploc, value == 2 ? pos : f.spawnloc, 3, "RESET");
496             game::spawneffect(PART_SPARK, vec(f.pos()).add(vec(0, 0, enttype[AFFINITY].radius*0.45f)), enttype[AFFINITY].radius*0.25f, TEAM(f.team, colour), 1);
497             game::spawneffect(PART_SPARK, vec(f.pos()).add(vec(0, 0, enttype[AFFINITY].radius*0.45f)), enttype[AFFINITY].radius*0.25f, colourwhite, 1);
498             game::spawneffect(PART_SPARK, value == 2 ? pos : vec(f.spawnloc).add(vec(0, 0, enttype[AFFINITY].radius*0.45f)), enttype[AFFINITY].radius*0.25f, TEAM(f.team, colour), 1);
499             game::spawneffect(PART_SPARK, value == 2 ? pos : vec(f.spawnloc).add(vec(0, 0, enttype[AFFINITY].radius*0.45f)), enttype[AFFINITY].radius*0.25f, colourwhite, 1);
500             game::announcef(S_V_FLAGRESET, CON_EVENT, NULL, true, "\faThe %s flag has been reset", game::colourteam(f.team, "flagtex"));
501         }
502         if(value == 2)
503         {
504             st.dropaffinity(i, pos, vec(0, 0, 1), lastmillis);
505             f.proj->stuck = 1;
506             f.proj->stick = NULL;
507         }
508         else st.returnaffinity(i, lastmillis);
509     }
510 
scoreaffinity(gameent * d,int relay,int goal,int score)511     void scoreaffinity(gameent *d, int relay, int goal, int score)
512     {
513         if(!st.flags.inrange(relay)) return;
514         float radius = enttype[AFFINITY].radius;
515         vec abovegoal, capturepos, returnpos;
516         capturestate::flag &f = st.flags[relay];
517         if(st.flags.inrange(goal))
518         {
519             capturestate::flag &g = st.flags[goal];
520             abovegoal = capturepos = g.spawnloc;
521         }
522         else abovegoal = capturepos = f.pos(true); // m_ctf_protect
523         returnpos = vec(f.spawnloc);
524         returnpos.add(vec(0, 0, radius*0.45f));
525         capturepos.add(vec(0, 0, radius*0.45f));
526         affinityeffect(goal, d->team, abovegoal, f.spawnloc, 3, "CAPTURED");
527         game::spawneffect(PART_SPARK, capturepos, radius*0.25f, TEAM(d->team, colour), 1);
528         game::spawneffect(PART_SPARK, capturepos, radius*0.25f, colourwhite, 1);
529         game::spawneffect(PART_SPARK, returnpos, radius*0.25f, TEAM(f.team, colour), 1);
530         game::spawneffect(PART_SPARK, returnpos, radius*0.25f, colourwhite, 1);
531         hud::teamscore(d->team).total = score;
532         defformatstring(fteam, "%s", game::colourteam(f.team, "flagtex"));
533         game::announcef(S_V_FLAGSCORE, CON_EVENT, d, true, "\fa%s captured the %s flag for team %s (score: \fs\fc%d\fS, time taken: \fs\fc%s\fS)", game::colourname(d), fteam, game::colourteam(d->team), score, timestr(lastmillis-f.taketime, 1));
534         st.returnaffinity(relay, lastmillis);
535     }
536 
takeaffinity(gameent * d,int i)537     void takeaffinity(gameent *d, int i)
538     {
539         if(!st.flags.inrange(i)) return;
540         capturestate::flag &f = st.flags[i];
541         playsound(S_CATCH, d->o, d);
542         affinityeffect(i, d->team, d->feetpos(), f.pos(true), 1, f.team == d->team ? "SECURED" : "TAKEN");
543         game::announcef(f.team == d->team ? S_V_FLAGSECURED : S_V_FLAGPICKUP, CON_EVENT, d, true, "\fa%s %s the %s flag", game::colourname(d), f.team == d->team ? "secured" : (f.droptime ? "picked up" : "stole"), game::colourteam(f.team, "flagtex"));
544         st.takeaffinity(i, d, lastmillis);
545         if(d->ai) aihomerun(d, d->ai->state.last());
546     }
547 
checkaffinity(gameent * d,int i)548     void checkaffinity(gameent *d, int i)
549     {
550         if(!canpickup(d, i)) return;
551         client::addmsg(N_TAKEAFFIN, "ri2", d->clientnum, i);
552         d->action[AC_AFFINITY] = false;
553     }
554 
update()555     void update()
556     {
557         gameent *d = NULL;
558         int numdyn = game::numdynents();
559         loopj(numdyn) if(((d = (gameent *)game::iterdynents(j))) && d->state == CS_ALIVE && (d == game::player1 || d->ai)) dropaffinity(d);
560         loopv(st.flags)
561         {
562             capturestate::flag &f = st.flags[i];
563             if(f.owner) continue;
564             if(f.droptime)
565             {
566                 f.droploc = f.pos();
567                 if(f.lastowner && (f.lastowner == game::player1 || f.lastowner->ai) && f.proj && (!f.movetime || totalmillis-f.movetime >= 40))
568                 {
569                     f.inertia = f.proj->vel;
570                     f.movetime = totalmillis-(totalmillis%40);
571                     client::addmsg(N_MOVEAFFIN, "ri8", f.lastowner->clientnum, i, int(f.droploc.x*DMF), int(f.droploc.y*DMF), int(f.droploc.z*DMF), int(f.inertia.x*DMF), int(f.inertia.y*DMF), int(f.inertia.z*DMF));
572                 }
573             }
574             loopj(numdyn) if(((d = (gameent *)game::iterdynents(j))) && d->state == CS_ALIVE && (d == game::player1 || d->ai)) checkaffinity(d, i);
575         }
576     }
577 
aiflagpos(gameent * d,capturestate::flag & f)578     vec &aiflagpos(gameent *d, capturestate::flag &f)
579     {
580         if(f.droptime || f.owner != d) return f.pos();
581         return f.spawnloc;
582     }
583 
aihomerun(gameent * d,ai::aistate & b)584     bool aihomerun(gameent *d, ai::aistate &b)
585     {
586         if(d->actortype < A_ENEMY && !m_ctf_protect(game::gamemode, game::mutators))
587         {
588             vec pos = d->feetpos();
589             loopk(2)
590             {
591                 int closest = -1;
592                 float closedist = 1e16f;
593                 loopv(st.flags)
594                 {
595                     capturestate::flag &f = st.flags[i];
596                     if(f.team == d->team && (k || ((!f.owner || f.owner == d) && !f.droptime)))
597                     {
598                         float dist = aiflagpos(d, f).squaredist(pos);
599                         if(closest < 0 || dist < closedist)
600                         {
601                             closest = i;
602                             closedist = dist;
603                         }
604                     }
605                 }
606                 if(st.flags.inrange(closest) && ai::makeroute(d, b, aiflagpos(d, st.flags[closest])))
607                 {
608                     d->ai->switchstate(b, ai::AI_S_PURSUE, ai::AI_T_AFFINITY, closest, ai::AI_A_HASTE);
609                     return true;
610                 }
611             }
612         }
613         if(b.type == ai::AI_S_PURSUE && b.targtype == ai::AI_T_NODE) return true; // we already did this..
614         if(ai::randomnode(d, b, ai::ALERTMIN, 1e16f))
615         {
616             d->ai->switchstate(b, ai::AI_S_PURSUE, ai::AI_T_NODE, d->ai->route[0], ai::AI_A_HASTE);
617             return true;
618         }
619         return false;
620     }
621 
aicheck(gameent * d,ai::aistate & b)622     bool aicheck(gameent *d, ai::aistate &b)
623     {
624         if(d->actortype != A_BOT) return false;
625         static vector<int> taken; taken.setsize(0);
626         loopv(st.flags)
627         {
628             capturestate::flag &g = st.flags[i];
629             if(g.owner == d)
630             {
631                 if(!m_ctf_protect(game::gamemode, game::mutators)) return aihomerun(d, b);
632             }
633             else if(g.team == d->team && (m_ctf_protect(game::gamemode, game::mutators) || (g.owner && g.owner->team != d->team) || g.droptime))
634                 taken.add(i);
635         }
636         if(!ai::badhealth(d)) while(!taken.empty())
637         {
638             int flag = taken.length() > 2 ? rnd(taken.length()) : 0;
639             if(ai::makeroute(d, b, aiflagpos(d, st.flags[taken[flag]])))
640             {
641                 d->ai->switchstate(b, ai::AI_S_PURSUE, ai::AI_T_AFFINITY, taken[flag], ai::AI_A_HASTE);
642                 return true;
643             }
644             else taken.remove(flag);
645         }
646         return false;
647     }
648 
aifind(gameent * d,ai::aistate & b,vector<ai::interest> & interests)649     void aifind(gameent *d, ai::aistate &b, vector<ai::interest> &interests)
650     {
651         vec pos = d->feetpos();
652         loopvj(st.flags)
653         {
654             capturestate::flag &f = st.flags[j];
655             bool home = f.team == d->team;
656             if(d->actortype == A_BOT && m_duke(game::gamemode, game::mutators) && home) continue;
657             static vector<int> targets; // build a list of others who are interested in this
658             targets.setsize(0);
659             bool regen = d->actortype != A_BOT || f.team == T_NEUTRAL || m_ctf_protect(game::gamemode, game::mutators) || !m_regen(game::gamemode, game::mutators) || d->health >= d->gethealth(game::gamemode, game::mutators);
660             ai::checkothers(targets, d, home || d->actortype != A_BOT ? ai::AI_S_DEFEND : ai::AI_S_PURSUE, ai::AI_T_AFFINITY, j, true);
661             if(d->actortype == A_BOT)
662             {
663                 gameent *e = NULL;
664                 int numdyns = game::numdynents();
665                 float mindist = enttype[AFFINITY].radius*4; mindist *= mindist;
666                 loopi(numdyns) if((e = (gameent *)game::iterdynents(i)) && !e->ai && e->state == CS_ALIVE && d->team == e->team)
667                 {
668                     if(targets.find(e->clientnum) < 0 && (f.owner == e || e->feetpos().squaredist(aiflagpos(d, f)) <= mindist))
669                         targets.add(e->clientnum);
670                 }
671             }
672             if(home)
673             {
674                 bool guard = false;
675                 if(f.owner || f.droptime || targets.empty()) guard = true;
676                 else if(d->hasweap(ai::weappref(d), m_weapon(d->actortype, game::gamemode, game::mutators)))
677                 { // see if we can relieve someone who only has a piece of crap
678                     gameent *t;
679                     loopvk(targets) if((t = game::getclient(targets[k])))
680                     {
681                         if((t->ai && !t->hasweap(ai::weappref(t), m_weapon(t->actortype, game::gamemode, game::mutators))) || (!t->ai && t->weapselect < W_OFFSET))
682                         {
683                             guard = true;
684                             break;
685                         }
686                     }
687                 }
688                 if(guard)
689                 { // defend the flag
690                     ai::interest &n = interests.add();
691                     n.state = ai::AI_S_DEFEND;
692                     n.node = ai::closestwaypoint(aiflagpos(d, f), ai::CLOSEDIST, true);
693                     n.target = j;
694                     n.targtype = ai::AI_T_AFFINITY;
695                     n.score = pos.squaredist(aiflagpos(d, f))/(!regen ? 100.f : 1.f);
696                     n.tolerance = 0.25f;
697                     n.team = true;
698                     n.acttype = ai::AI_A_PROTECT;
699                 }
700             }
701             else
702             {
703                 if(targets.empty())
704                 { // attack the flag
705                     ai::interest &n = interests.add();
706                     n.state = d->actortype == A_BOT ? ai::AI_S_PURSUE : ai::AI_S_DEFEND;
707                     n.node = ai::closestwaypoint(aiflagpos(d, f), ai::CLOSEDIST, true);
708                     n.target = j;
709                     n.targtype = ai::AI_T_AFFINITY;
710                     n.score = pos.squaredist(aiflagpos(d, f));
711                     n.tolerance = 0.25f;
712                     n.team = true;
713                 }
714                 else
715                 { // help by defending the attacker
716                     gameent *t;
717                     loopvk(targets) if((t = game::getclient(targets[k])))
718                     {
719                         ai::interest &n = interests.add();
720                         bool team = d->team == t->team;
721                         n.state = team ? ai::AI_S_DEFEND : ai::AI_S_PURSUE;
722                         n.node = t->lastnode;
723                         n.target = t->clientnum;
724                         n.targtype = ai::AI_T_ACTOR;
725                         n.score = d->o.squaredist(t->o);
726                         n.tolerance = 0.25f;
727                         n.team = team;
728                         if(team) n.acttype = ai::AI_A_PROTECT;
729                     }
730                 }
731             }
732         }
733     }
734 
aidefense(gameent * d,ai::aistate & b)735     bool aidefense(gameent *d, ai::aistate &b)
736     {
737         if(d->actortype == A_BOT)
738         {
739             if(!m_ctf_protect(game::gamemode, game::mutators)) loopv(st.flags) if(st.flags[i].owner == d) return aihomerun(d, b);
740             if(d->actortype == A_BOT && m_duke(game::gamemode, game::mutators) && b.owner < 0) return false;
741         }
742         if(st.flags.inrange(b.target))
743         {
744             capturestate::flag &f = st.flags[b.target];
745             if(f.team == d->team && f.owner && f.owner->team != d->team && ai::violence(d, b, f.owner, 4)) return true;
746             int walk = f.owner && f.owner->team != d->team ? 1 : 0;
747             if(d->actortype == A_BOT)
748             {
749                 if((!m_regen(game::gamemode, game::mutators) || d->health >= d->gethealth(game::gamemode, game::mutators)) && lastmillis-b.millis >= (201-d->skill)*33)
750                 {
751                     if(b.owner < 0)
752                     {
753                         static vector<int> targets; // build a list of others who are interested in this
754                         targets.setsize(0);
755                         ai::checkothers(targets, d, ai::AI_S_DEFEND, ai::AI_T_AFFINITY, b.target, true);
756                         gameent *e = NULL;
757                         int numdyns = game::numdynents();
758                         float mindist = enttype[AFFINITY].radius*4; mindist *= mindist;
759                         loopi(numdyns) if((e = (gameent *)game::iterdynents(i)) && !e->ai && e->state == CS_ALIVE && d->team == e->team)
760                         {
761                             if(targets.find(e->clientnum) < 0 && (f.owner == e || e->feetpos().squaredist(aiflagpos(d, f)) <= mindist))
762                                 targets.add(e->clientnum);
763                         }
764                         if(!targets.empty())
765                         {
766                             d->ai->tryreset = true; // re-evaluate so as not to herd
767                             return true;
768                         }
769                         else
770                         {
771                             walk = 2;
772                             b.millis = lastmillis;
773                         }
774                     }
775                     else
776                     {
777                         walk = 2;
778                         b.millis = lastmillis;
779                     }
780                 }
781                 vec pos = d->feetpos();
782                 float mindist = enttype[AFFINITY].radius*8; mindist *= mindist;
783                 loopv(st.flags)
784                 { // get out of the way of the returnee!
785                     capturestate::flag &g = st.flags[i];
786                     if(pos.squaredist(aiflagpos(d, g)) <= mindist)
787                     {
788                         if(g.owner && g.owner->team == d->team && !walk) walk = 1;
789                         if(g.droptime && ai::makeroute(d, b, aiflagpos(d, g))) return true;
790                     }
791                 }
792             }
793             return ai::defense(d, b, aiflagpos(d, f), enttype[AFFINITY].radius, enttype[AFFINITY].radius*walk*8, walk);
794         }
795         return false;
796     }
797 
aicheckpos(gameent * d,ai::aistate & b)798     bool aicheckpos(gameent *d, ai::aistate &b)
799     {
800         return false;
801     }
802 
aipursue(gameent * d,ai::aistate & b)803     bool aipursue(gameent *d, ai::aistate &b)
804     {
805         if(!st.flags.inrange(b.target) || d->actortype != A_BOT) return false;
806         capturestate::flag &f = st.flags[b.target];
807         if(f.team != d->team)
808         {
809             if(f.owner)
810             {
811                 if(d == f.owner) return aihomerun(d, b);
812                 else if(d->team != f.owner->team) return ai::violence(d, b, f.owner, 4);
813                 else return ai::defense(d, b, aiflagpos(d, f));
814             }
815             return ai::makeroute(d, b, aiflagpos(d, f));
816         }
817         else loopv(st.flags) if(st.flags[i].owner == d && ai::makeroute(d, b, aiflagpos(d, st.flags[i])))
818         {
819             b.acttype = ai::AI_A_HASTE;
820             return true;
821         }
822         if(b.owner >= 0) return ai::makeroute(d, b, aiflagpos(d, f));
823         return false;
824     }
825 }
826