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