1 #include "game.h"
2 namespace stf
3 {
4 	stfstate st;
5 
insideflag(const stfstate::flag & b,gameent * d)6     bool insideflag(const stfstate::flag &b, gameent *d)
7     {
8         return st.insideflag(b, d->feetpos());
9     }
10 
preload()11     void preload()
12     {
13         loopi(TEAM_MAX) loadmodel(teamtype[i].flag, -1, true);
14     }
15 
render()16 	void render()
17 	{
18 		loopv(st.flags)
19 		{
20 			stfstate::flag &b = st.flags[i];
21             if(!entities::ents.inrange(b.ent)) continue;
22 			const char *flagname = teamtype[b.owner].flag;
23             rendermodel(&entities::ents[b.ent]->light, flagname, ANIM_MAPMODEL|ANIM_LOOP, b.o, entities::ents[b.ent]->attrs[2], entities::ents[b.ent]->attrs[3], 0, MDL_SHADOW|MDL_CULL_VFC|MDL_CULL_OCCLUDED);
24 			int attack = b.enemy ? b.enemy : b.owner, defend = b.owner ? b.owner : b.enemy;
25 			if(b.enemy && b.owner)
26 				formatstring(b.info)("<super>\fs%s%s\fS vs. \fs%s%s\fS", teamtype[b.owner].chat, teamtype[b.owner].name, teamtype[b.enemy].chat, teamtype[b.enemy].name);
27 			else formatstring(b.info)("<super>\fs%s%s\fS", teamtype[defend].chat, teamtype[defend].name);
28 			float occupy = attack ? (!b.owner || b.enemy ? clamp(b.converted/float((!stfstyle && b.owner ? 2 : 1)*stfoccupy), 0.f, 1.f) : 1.f) : 0.f;
29 			vec above = b.o;
30 			above.z += enttype[FLAG].radius*2/3;
31 			part_text(above, b.info);
32 			above.z += 2.5f;
33 			if(b.enemy)
34 			{
35 				part_icon(above, textureload(hud::progresstex, 3), 4, 1, 0, 0, 1, teamtype[b.enemy].colour, 0, occupy);
36 				part_icon(above, textureload(hud::progresstex, 3), 4, 1, 0, 0, 1, teamtype[b.owner].colour, occupy, 1-occupy);
37 			}
38 			else part_icon(above, textureload(hud::progresstex, 3), 4, 1, 0, 0, 1, teamtype[b.owner].colour);
39 			defformatstring(str)("%d%%", int(occupy*100.f)); part_textcopy(above, str);
40 		}
41 	}
42 
43 
adddynlights()44     void adddynlights()
45     {
46         loopv(st.flags)
47         {
48             stfstate::flag &f = st.flags[i];
49             if(!entities::ents.inrange(f.ent)) continue;
50 			adddynlight(vec(f.o).add(vec(0, 0, enttype[FLAG].radius)), enttype[FLAG].radius*1.5f,
51 				vec((teamtype[f.owner].colour>>16), ((teamtype[f.owner].colour>>8)&0xFF), (teamtype[f.owner].colour&0xFF)).div(255.f));
52         }
53     }
54 
drawblips(int w,int h,float blend)55 	void drawblips(int w, int h, float blend)
56 	{
57 		loopv(st.flags)
58 		{
59 			stfstate::flag &f = st.flags[i];
60 			vec dir(f.o); dir.sub(camera1->o);
61 			int colour = teamtype[f.enemy && lastmillis%600 >= 400 ? f.enemy : f.owner].colour;
62 			float r = (colour>>16)/255.f, g = ((colour>>8)&0xFF)/255.f, b = (colour&0xFF)/255.f, fade = blend*hud::radarflagblend;
63 			if(f.owner != game::player1->team && f.enemy != game::player1->team)
64 			{
65 				float dist = dir.magnitude(),
66 					diff = dist <= hud::radarrange() ? clamp(1.f-(dist/hud::radarrange()), 0.f, 1.f) : 0.f;
67 				fade *= diff*0.5f;
68 			}
69 			dir.rotate_around_z(-camera1->yaw*RAD); dir.normalize();
70 			const char *tex = f.hasflag ? hud::arrowtex : hud::flagtex;
71 			float size = hud::radarflagsize*(f.hasflag ? 2 : 1);
72 			if(hud::radarflagnames > (f.hasflag ? 0 : 1))
73 			{
74 				float occupy = !f.owner || f.enemy ? clamp(f.converted/float((!stfstyle && f.owner ? 2 : 1) * stfoccupy), 0.f, 1.f) : 1.f;
75 				bool overthrow = f.owner && f.enemy == game::player1->team;
76 				if(occupy < 1.f) hud::drawblip(tex, 3, w, h, size, fade, dir, r, g, b, "radar", "%s%d%%", f.hasflag ? (overthrow ? "\fo" : (occupy < 1.f ? "\fy" : "\fg")) : teamtype[f.owner].chat, int(occupy*100.f));
77 				else hud::drawblip(tex, 3, w, h, size, fade, dir, r, g, b, "radar", "%s%s", f.hasflag ? (overthrow ? "\fo" : (occupy < 1.f ? "\fy" : "\fg")) : teamtype[f.owner].chat, teamtype[f.owner].name);
78 			}
79 			else hud::drawblip(tex, 3, w, h, size, fade, dir, r, g, b);
80 		}
81 	}
82 
drawlast(int w,int h,int & tx,int & ty,float blend)83 	void drawlast(int w, int h, int &tx, int &ty, float blend)
84 	{
85 		if(game::player1->state == CS_ALIVE && hud::shownotices >= 3)
86 		{
87 			loopv(st.flags) if(insideflag(st.flags[i], game::player1) && (st.flags[i].owner == game::player1->team || st.flags[i].enemy == game::player1->team))
88 			{
89 				stfstate::flag &f = st.flags[i];
90 				pushfont("super");
91 				float occupy = !f.owner || f.enemy ? clamp(f.converted/float((!stfstyle && f.owner ? 2 : 1) * stfoccupy), 0.f, 1.f) : 1.f;
92 				bool overthrow = f.owner && f.enemy == game::player1->team;
93 				ty += draw_textx("\fzwa%s \fs%s%d%%\fS complete", tx, ty, 255, 255, 255, int(255*blend), TEXT_CENTERED, -1, -1, overthrow ? "Overthrow" : "Secure", overthrow ? "\fo" : (occupy < 1.f ? "\fy" : "\fg"), int(occupy*100.f))*hud::noticescale;
94 				popfont();
95 				break;
96 			}
97 		}
98 	}
99 
drawinventory(int x,int y,int s,int m,float blend)100     int drawinventory(int x, int y, int s, int m, float blend)
101     {
102 		int sy = 0;
103 		loopv(st.flags)
104 		{
105 			if(y-sy-s < m) break;
106 			stfstate::flag &f = st.flags[i];
107 			bool hasflag = game::player1->state == CS_ALIVE && insideflag(f, game::player1);
108 			if(f.hasflag != hasflag) { f.hasflag = hasflag; f.lasthad = lastmillis-max(1000-(lastmillis-f.lasthad), 0); }
109 			int millis = lastmillis-f.lasthad;
110 			bool headsup = game::player1->state == CS_SPECTATOR || hud::inventorygame >= (f.owner == game::player1->team || st.flags.length() == 1 ? 1 : 2);
111 			if(headsup || f.hasflag || millis <= 1000)
112 			{
113 				int prevsy = sy, colour = teamtype[f.owner].colour; bool skewed = false;
114 				float skew = headsup ? hud::inventoryskew : 0.f, fade = blend*hud::inventoryblend,
115 					occupy = f.enemy ? clamp(f.converted/float((!stfstyle && f.owner ? 2 : 1)*stfoccupy), 0.f, 1.f) : (f.owner ? 1.f : 0.f),
116 					r = (colour>>16)/255.f, g = ((colour>>8)&0xFF)/255.f, b = (colour&0xFF)/255.f;
117 				if(f.hasflag)
118 				{
119 					skewed = true;
120 					skew += (millis <= 1000 ? clamp(float(millis)/1000.f, 0.f, 1.f)*(1.f-skew) : 1.f-skew);
121 					if(millis > 1000)
122 					{
123 						float pc = (millis%1000)/500.f, amt = pc > 1 ? 2.f-pc : pc;
124 						fade += (1.f-fade)*amt;
125 					}
126 				}
127 				else if(millis <= 1000)
128 				{
129 					skewed = true;
130 					skew += (1.f-skew)-(clamp(float(millis)/1000.f, 0.f, 1.f)*(1.f-skew));
131 				}
132 				sy += hud::drawitem(hud::flagtex, x, y-sy, s, false, r, g, b, fade, skew);
133 				if(f.enemy)
134 				{
135 					int colour2 = teamtype[f.enemy].colour;
136 					float r2 = (colour2>>16)/255.f, g2 = ((colour2>>8)&0xFF)/255.f, b2 = (colour2&0xFF)/255.f;
137 					hud::drawprogress(x, y-prevsy, 0, occupy, int(s*0.5f), false, r2, g2, b2, fade, skew);
138 					hud::drawprogress(x, y-prevsy, occupy, 1-occupy, int(s*0.5f), false, r, g, b, fade, skew, !skewed && headsup ? "sub" : "radar", "%s%d%%", hasflag ? (f.owner && f.enemy == game::player1->team ? "\fo" : (occupy < 1.f ? "\fy" : "\fg")) : "\fw", int(occupy*100.f));
139 				}
140 				else if(f.owner) hud::drawitem(hud::teamtex(f.owner), x, y-prevsy, int(s*0.5f), false, 1.f, 1.f, 1.f, fade, skew);
141 			}
142 		}
143         return sy;
144     }
145 
setupflags()146 	void setupflags()
147 	{
148 		st.reset();
149 		loopv(entities::ents)
150 		{
151 			extentity *e = entities::ents[i];
152 			if(e->type != FLAG || !m_check(e->attrs[3], game::gamemode)) continue;
153 			stfstate::flag &b = st.flags.add();
154 			b.o = e->o;
155 			defformatstring(alias)("flag_%d", e->attrs[4]);
156 			const char *name = getalias(alias);
157 			if(name[0]) copystring(b.name, name);
158 			else formatstring(b.name)("flag %d", st.flags.length());
159 			b.ent = i;
160 		}
161 	}
162 
sendflags(packetbuf & p)163 	void sendflags(packetbuf &p)
164 	{
165 		putint(p, SV_FLAGS);
166 		putint(p, st.flags.length());
167 		loopv(st.flags)
168 		{
169 			stfstate::flag &b = st.flags[i];
170 			putint(p, int(b.o.x*DMF));
171 			putint(p, int(b.o.y*DMF));
172 			putint(p, int(b.o.z*DMF));
173 		}
174 	}
175 
updateflag(int i,int owner,int enemy,int converted)176 	void updateflag(int i, int owner, int enemy, int converted)
177 	{
178 		if(!st.flags.inrange(i)) return;
179 		stfstate::flag &b = st.flags[i];
180 		if(owner)
181 		{
182 			if(b.owner != owner)
183 			{
184 				gameent *d = NULL, *e = NULL;
185 				loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && e->type == ENT_PLAYER && insideflag(b, e))
186 					if((d = e) == game::player1) break;
187 				game::announce(S_V_FLAGSECURED, d == game::player1 ? CON_SELF : CON_INFO, d, "\fateam \fs%s%s\fS secured %s", teamtype[owner].chat, teamtype[owner].name, b.name);
188 				defformatstring(text)("<super>%s\fzReSECURED", teamtype[owner].chat);
189 				part_textcopy(vec(b.o).add(vec(0, 0, enttype[FLAG].radius)), text, PART_TEXT, game::aboveheadfade, 0xFFFFFF, 3, 1, -10);
190 				game::spawneffect(PART_FIREBALL, vec(b.o).add(vec(0, 0, enttype[FLAG].radius/2)), teamtype[owner].colour, enttype[FLAG].radius*2);
191 			}
192 		}
193 		else if(b.owner)
194 		{
195 			gameent *d = NULL, *e = NULL;
196 			loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && e->type == ENT_PLAYER && insideflag(b, e))
197 				if((d = e) == game::player1) break;
198 			game::announce(S_V_FLAGOVERTHROWN, d == game::player1 ? CON_SELF : CON_INFO, d, "\fateam \fs%s%s\fS overthrew %s", teamtype[enemy].chat, teamtype[enemy].name, b.name);
199 			defformatstring(text)("<super>%s\fzReOVERTHROWN", teamtype[enemy].chat);
200 			part_textcopy(vec(b.o).add(vec(0, 0, enttype[FLAG].radius)), text, PART_TEXT, game::aboveheadfade, 0xFFFFFF, 3, 1, -10);
201 			game::spawneffect(PART_FIREBALL, vec(b.o).add(vec(0, 0, enttype[FLAG].radius/2)), teamtype[enemy].colour, enttype[FLAG].radius*2);
202 		}
203 		b.owner = owner;
204 		b.enemy = enemy;
205 		b.converted = converted;
206 	}
207 
setscore(int team,int total)208 	void setscore(int team, int total)
209 	{
210 		st.findscore(team).total = total;
211 	}
212 
aiowner(gameent * d)213 	int aiowner(gameent *d)
214 	{
215 		loopv(st.flags) if(entities::ents.inrange(st.flags[i].ent) && entities::ents[d->aientity]->links.find(st.flags[i].ent) >= 0)
216 			return st.flags[i].owner ? st.flags[i].owner : st.flags[i].enemy;
217 		return d->team;
218 	}
219 
aicheck(gameent * d,ai::aistate & b)220 	bool aicheck(gameent *d, ai::aistate &b)
221 	{
222 		return false;
223 	}
224 
aifind(gameent * d,ai::aistate & b,vector<ai::interest> & interests)225 	void aifind(gameent *d, ai::aistate &b, vector<ai::interest> &interests)
226 	{
227 		if(d->aitype == AI_BOT)
228 		{
229 			vec pos = d->feetpos();
230 			loopvj(st.flags)
231 			{
232 				stfstate::flag &f = st.flags[j];
233 				static vector<int> targets; // build a list of others who are interested in this
234 				targets.setsizenodelete(0);
235 				ai::checkothers(targets, d, ai::AI_S_DEFEND, ai::AI_T_AFFINITY, j, true);
236 				gameent *e = NULL;
237 				bool regen = !m_regen(game::gamemode, game::mutators) || d->health >= max(maxhealth, extrahealth);
238 				loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && !e->ai && ai::owner(d) == ai::owner(e) && ai::targetable(d, e, false))
239 				{
240 					vec ep = e->feetpos();
241 					if(targets.find(e->clientnum) < 0 && ep.squaredist(f.o) <= (enttype[FLAG].radius*enttype[FLAG].radius))
242 						targets.add(e->clientnum);
243 				}
244 				if((!regen && f.owner == ai::owner(d)) || (targets.empty() && (f.owner != ai::owner(d) || f.enemy)))
245 				{
246 					ai::interest &n = interests.add();
247 					n.state = ai::AI_S_DEFEND;
248 					n.node = entities::closestent(WAYPOINT, f.o, ai::NEARDIST, false);
249 					n.target = j;
250 					n.targtype = ai::AI_T_AFFINITY;
251 					n.score = pos.squaredist(f.o)/(!regen ? 100.f : 1.f);
252 				}
253 			}
254 		}
255 	}
256 
aidefend(gameent * d,ai::aistate & b)257 	bool aidefend(gameent *d, ai::aistate &b)
258 	{
259 		if(st.flags.inrange(b.target))
260 		{
261 			stfstate::flag &f = st.flags[b.target];
262 			bool regen = d->aitype != AI_BOT || !m_regen(game::gamemode, game::mutators) || d->health >= max(maxhealth, extrahealth);
263 			int walk = f.enemy && f.enemy != ai::owner(d) ? 1 : 0;
264 			if(regen && (!f.enemy && ai::owner(d) == f.owner))
265 			{
266 				static vector<int> targets; // build a list of others who are interested in this
267 				targets.setsizenodelete(0);
268 				ai::checkothers(targets, d, ai::AI_S_DEFEND, ai::AI_T_AFFINITY, b.target, true);
269 				if(d->aitype == AI_BOT)
270 				{
271 					gameent *e = NULL;
272 					loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && !e->ai && ai::owner(d) == ai::owner(e) && ai::targetable(d, e, false))
273 					{
274 						vec ep = e->feetpos();
275 						if(targets.find(e->clientnum) < 0 && (ep.squaredist(f.o) <= (enttype[FLAG].radius*enttype[FLAG].radius*4)))
276 							targets.add(e->clientnum);
277 					}
278 				}
279 				if(!targets.empty())
280 				{
281 					if(lastmillis-b.millis >= (201-d->skill)*33)
282 					{
283 						d->ai->trywipe = true; // re-evaluate so as not to herd
284 						return true;
285 					}
286 					else walk = 2;
287 				}
288 				else walk = 1;
289 			}
290 			return ai::defend(d, b, f.o, !f.enemy ? ai::CLOSEDIST : float(enttype[FLAG].radius), !f.enemy ? ai::NEARDIST : float(enttype[FLAG].radius*(1+walk)), walk);
291 		}
292 		return false;
293 	}
294 
aipursue(gameent * d,ai::aistate & b)295 	bool aipursue(gameent *d, ai::aistate &b)
296 	{
297 		b.type = ai::AI_S_DEFEND;
298 		return aidefend(d, b);
299 	}
300 
removeplayer(gameent * d)301     void removeplayer(gameent *d)
302     {
303     }
304 }
305