Xonotic
breakable.qc
Go to the documentation of this file.
1 #include "breakable.qh"
2 #ifdef SVQC
3 
4 #include <server/damage.qh>
5 #include <server/bot/api.qh>
9 
10 .entity sprite;
11 
12 .float dmg;
13 .float dmg_edge;
14 .float dmg_radius;
15 .float dmg_force;
16 .float debrismovetype;
17 .float debrissolid;
18 .vector debrisvelocity;
19 .vector debrisvelocityjitter;
20 .vector debrisavelocityjitter;
21 .float debristime;
22 .float debristimejitter;
23 .float debrisfadetime;
24 .float debrisdamageforcescale;
25 .float debrisskin;
26 
27 .string mdl_dead; // or "" to hide when broken
28 .string debris; // space separated list of debris models
29 // other fields:
30 // mdl = particle effect name
31 // count = particle effect multiplier
32 // targetname = target to trigger to unbreak the model
33 // target = targets to trigger when broken
34 // health = amount of damage it can take
35 // spawnflags:
36 // START_DISABLED: needs to be triggered to activate
37 // BREAKABLE_INDICATE_DAMAGE: indicate damage
38 // BREAKABLE_NODAMAGE: don't take direct damage (needs to be triggered to 'explode', then triggered again to restore)
39 // NOSPLASH: don't take splash damage
40 // notes:
41 // for mdl_dead to work, origin must be set (using a common/origin brush).
42 // Otherwise mdl_dead will be displayed at the map origin, and nobody would
43 // want that!
44 
45 void func_breakable_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
46 
47 //
48 // func_breakable
49 // - basically func_assault_destructible for general gameplay use
50 //
51 void LaunchDebris (entity this, string debrisname, vector force)
52 {
53  entity dbr = new(debris);
54  vector org = this.absmin
55  + '1 0 0' * random() * (this.absmax.x - this.absmin.x)
56  + '0 1 0' * random() * (this.absmax.y - this.absmin.y)
57  + '0 0 1' * random() * (this.absmax.z - this.absmin.z);
58  setorigin(dbr, org);
59  _setmodel (dbr, debrisname );
60  dbr.skin = this.debrisskin;
61  dbr.colormap = this.colormap; // inherit team colors
62  dbr.owner = this; // do not be affected by our own explosion
63  set_movetype(dbr, this.debrismovetype);
64  dbr.solid = this.debrissolid;
65  if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out
66  setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it
67  dbr.velocity_x = this.debrisvelocity.x + this.debrisvelocityjitter.x * crandom();
68  dbr.velocity_y = this.debrisvelocity.y + this.debrisvelocityjitter.y * crandom();
69  dbr.velocity_z = this.debrisvelocity.z + this.debrisvelocityjitter.z * crandom();
70  dbr.velocity = dbr.velocity + force * this.debrisdamageforcescale;
71  dbr.angles = this.angles;
72  dbr.avelocity_x = random()*this.debrisavelocityjitter.x;
73  dbr.avelocity_y = random()*this.debrisavelocityjitter.y;
74  dbr.avelocity_z = random()*this.debrisavelocityjitter.z;
75  dbr.damageforcescale = this.debrisdamageforcescale;
76  if(dbr.damageforcescale)
77  dbr.takedamage = DAMAGE_YES;
78  SUB_SetFade(dbr, time + this.debristime + crandom() * this.debristimejitter, this.debrisfadetime);
79 }
80 
81 void func_breakable_colormod(entity this)
82 {
83  float h;
85  return;
86  h = GetResource(this, RES_HEALTH) / this.max_health;
87  if(h < 0.25)
88  this.colormod = '1 0 0';
89  else if(h <= 0.75)
90  this.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5);
91  else
92  this.colormod = '1 1 1';
93 }
94 
95 void func_breakable_look_destroyed(entity this)
96 {
97  float floorZ;
98 
99  if(this.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first
100  this.dropped_origin = this.origin;
101 
102  if(this.mdl_dead == "")
103  this.effects |= EF_NODRAW;
104  else {
105  if (this.origin == '0 0 0') { // probably no origin brush, so don't spawn in the middle of the map..
106  floorZ = this.absmin.z;
107  setorigin(this, ((this.absmax + this.absmin) * 0.5));
108  this.origin_z = floorZ;
109  }
110  _setmodel(this, this.mdl_dead);
111  ApplyMinMaxScaleAngles(this);
112  this.effects &= ~EF_NODRAW;
113  }
114 
115  this.solid = SOLID_NOT;
116 }
117 
118 void func_breakable_look_restore(entity this)
119 {
120  _setmodel(this, this.mdl);
121  ApplyMinMaxScaleAngles(this);
122  this.effects &= ~EF_NODRAW;
123 
124  if(this.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow
125  setorigin(this, this.dropped_origin);
126 
127  this.solid = SOLID_BSP;
128 }
129 
130 void func_breakable_behave_destroyed(entity this)
131 {
133  this.takedamage = DAMAGE_NO;
134  if(this.bot_attack)
135  IL_REMOVE(g_bot_targets, this);
136  this.bot_attack = false;
137  this.event_damage = func_null;
138  this.state = STATE_BROKEN;
139  if(this.spawnflags & BREAKABLE_NODAMAGE)
140  this.use = func_null;
141  func_breakable_colormod(this);
142  if (this.noise1)
143  stopsound (this, CH_TRIGGER_SINGLE);
144 
145  IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this,
146  {
147  RemoveHook(it);
148  });
149 }
150 
151 void func_breakable_think(entity this)
152 {
153  this.nextthink = time;
154  CSQCMODEL_AUTOUPDATE(this);
155 }
156 
157 void func_breakable_destroy(entity this, entity actor, entity trigger);
158 void func_breakable_behave_restore(entity this)
159 {
161  if(this.sprite)
162  {
163  WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
164  WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
165  }
166  if(!(this.spawnflags & BREAKABLE_NODAMAGE))
167  {
168  this.takedamage = DAMAGE_AIM;
169  if(!this.bot_attack)
170  IL_PUSH(g_bot_targets, this);
171  this.bot_attack = true;
172  this.event_damage = func_breakable_damage;
173  }
174  if(this.spawnflags & BREAKABLE_NODAMAGE)
175  this.use = func_breakable_destroy; // don't need to set it usually, as .use isn't reset
176  this.state = STATE_ALIVE;
177  //this.nextthink = 0; // cancel auto respawn
178  setthink(this, func_breakable_think);
179  this.nextthink = time + 0.1;
180  func_breakable_colormod(this);
181  if (this.noise1)
183 }
184 
185 void func_breakable_init_for_player(entity this, entity player)
186 {
187  if (this.noise1 && this.state == STATE_ALIVE && IS_REAL_CLIENT(player))
188  {
189  msg_entity = player;
190  soundto (MSG_ONE, this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM, 0);
191  }
192 }
193 
194 void func_breakable_destroyed(entity this)
195 {
196  func_breakable_look_destroyed(this);
197  func_breakable_behave_destroyed(this);
198 }
199 
200 void func_breakable_restore(entity this, entity actor, entity trigger)
201 {
202  func_breakable_look_restore(this);
203  func_breakable_behave_restore(this);
204 }
205 
206 void func_breakable_restore_self(entity this)
207 {
208  // TODO: use a clipgroup for all func_breakables so they don't collide with eachother
209  float oldhit = this.dphitcontentsmask;
210  this.dphitcontentsmask = DPCONTENTS_BODY; // we really only care about when players are standing inside, obey the mapper in other cases!
211  tracebox(this.origin, this.mins, this.maxs, this.origin, MOVE_NORMAL, this);
212  this.dphitcontentsmask = oldhit;
214  {
215  this.nextthink = time + 5; // retry every 5 seconds until the area becomes clear
216  return;
217  }
218  func_breakable_restore(this, NULL, NULL);
219 }
220 
221 vector debrisforce; // global, set before calling this
222 void func_breakable_destroy(entity this, entity actor, entity trigger)
223 {
224  float n, i;
225  string oldmsg;
226 
227  entity act = this.owner;
228  this.owner = NULL; // set by W_PrepareExplosionByDamage
229 
230  // now throw around the debris
231  n = tokenize_console(this.debris);
232  for(i = 0; i < n; ++i)
233  LaunchDebris(this, argv(i), debrisforce);
234 
235  func_breakable_destroyed(this);
236 
237  if(this.noise)
238  _sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
239 
240  if(this.dmg)
241  RadiusDamage(this, act, this.dmg, this.dmg_edge, this.dmg_radius, this, NULL, this.dmg_force, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, NULL);
242 
243  if(this.cnt) // TODO
244  __pointparticles(this.cnt, this.absmin * 0.5 + this.absmax * 0.5, '0 0 0', this.count);
245 
246  if(this.respawntime)
247  {
248  CSQCMODEL_AUTOUPDATE(this);
249  setthink(this, func_breakable_restore_self);
250  this.nextthink = time + this.respawntime + crandom() * this.respawntimejitter;
251  }
252 
253  oldmsg = this.message;
254  this.message = "";
255  SUB_UseTargets(this, act, trigger);
256  this.message = oldmsg;
257 }
258 
259 void func_breakable_destroy_self(entity this)
260 {
261  func_breakable_destroy(this, NULL, NULL);
262 }
263 
264 void func_breakable_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
265 {
266  if(this.state == STATE_BROKEN)
267  return;
268  if(this.spawnflags & NOSPLASH)
269  if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
270  return;
271  if(this.team)
272  if(attacker.team == this.team)
273  return;
274  this.pain_finished = time;
275  TakeResource(this, RES_HEALTH, damage);
276  if(this.sprite)
277  {
278  WaypointSprite_Ping(this.sprite);
279  WaypointSprite_UpdateHealth(this.sprite, GetResource(this, RES_HEALTH));
280  }
281  func_breakable_colormod(this);
282 
283  if(GetResource(this, RES_HEALTH) <= 0)
284  {
285  debrisforce = force;
286 
287  this.takedamage = DAMAGE_NO;
288  this.event_damage = func_null;
289 
290  if(IS_CLIENT(attacker)) //&& this.classname == "func_assault_destructible")
291  {
292  this.owner = attacker;
293  this.realowner = attacker;
294  }
295 
296  // do not explode NOW but in the NEXT FRAME!
297  // because recursive calls to RadiusDamage are not allowed
298  this.nextthink = time;
299  CSQCMODEL_AUTOUPDATE(this);
300  setthink(this, func_breakable_destroy_self);
301  }
302 }
303 
304 void func_breakable_reset(entity this)
305 {
306  this.team = this.team_saved;
307  func_breakable_look_restore(this);
308  if(this.spawnflags & START_DISABLED)
309  func_breakable_behave_destroyed(this);
310  else
311  func_breakable_behave_restore(this);
312 }
313 
314 void func_breakable_setup(entity this)
315 {
316  float n, i;
317  if(!GetResource(this, RES_HEALTH))
318  SetResourceExplicit(this, RES_HEALTH, 100);
319  this.max_health = GetResource(this, RES_HEALTH);
320 
321  // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
322  if(!this.debrismovetype) this.debrismovetype = MOVETYPE_BOUNCE;
323  if(!this.debrissolid) this.debrissolid = SOLID_NOT;
324  if(this.debrisvelocity == '0 0 0') this.debrisvelocity = '0 0 140';
325  if(this.debrisvelocityjitter == '0 0 0') this.debrisvelocityjitter = '70 70 70';
326  if(this.debrisavelocityjitter == '0 0 0') this.debrisavelocityjitter = '600 600 600';
327  if(!this.debristime) this.debristime = 3.5;
328  if(!this.debristimejitter) this.debristime = 2.5;
329 
330  if(this.mdl != "")
331  this.cnt = _particleeffectnum(this.mdl);
332  if(this.count == 0)
333  this.count = 1;
334 
335  if(this.message == "")
336  this.message = "got too close to an explosion";
337  if(this.message2 == "")
338  this.message2 = "was pushed into an explosion by";
339  if(!this.dmg_radius)
340  this.dmg_radius = 150;
341  if(!this.dmg_force)
342  this.dmg_force = 200;
343 
344  this.mdl = this.model;
345  SetBrushEntityModel(this, true);
346 
347  if(this.spawnflags & BREAKABLE_NODAMAGE)
348  this.use = func_breakable_destroy;
349  else
350  this.use = func_breakable_restore;
351 
352  if(this.spawnflags & BREAKABLE_NODAMAGE)
353  {
354  this.takedamage = DAMAGE_NO;
355  this.event_damage = func_null;
356  this.bot_attack = false;
357  }
358 
359  // precache all the models
360  if (this.mdl_dead)
361  precache_model(this.mdl_dead);
362  n = tokenize_console(this.debris);
363  for(i = 0; i < n; ++i)
364  precache_model(argv(i));
365  if(this.noise)
366  precache_sound(this.noise);
367  if(this.noise1)
368  precache_sound(this.noise1);
369 
370  this.team_saved = this.team;
371  IL_PUSH(g_saved_team, this);
372  this.dropped_origin = this.origin;
373 
374  this.reset = func_breakable_reset;
375  this.reset(this);
376 
377  IL_PUSH(g_initforplayer, this);
378  this.init_for_player = func_breakable_init_for_player;
379 
380  CSQCMODEL_AUTOINIT(this);
381 }
382 
383 // for use in maps with a "model" key set
384 spawnfunc(misc_breakablemodel) { func_breakable_setup(this); }
385 
386 // destructible walls that can be used to trigger target_objective_decrease
387 spawnfunc(func_breakable) { func_breakable_setup(this); }
388 #endif
const int HITTYPE_SPLASH
automatically set by RadiusDamage
Definition: all.qh:27
const float SOLID_NOT
Definition: csprogsdefs.qc:244
float state
Definition: subs.qh:32
ERASEABLE void IL_REMOVE(IntrusiveList this, entity it)
Remove any element, anywhere in the list.
#define IL_EACH(this, cond, body)
const int BREAKABLE_INDICATE_DAMAGE
Definition: breakable.qh:4
float colormap
Definition: csprogsdefs.qc:131
const int NOSPLASH
Definition: defs.qh:12
entity sprite
Definition: sv_assault.qc:11
float dmg_radius
Definition: damage.qh:40
float respawntimejitter
Definition: items.qh:37
float respawntime
Definition: items.qh:36
vector colormod
Definition: powerups.qc:21
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
Definition: cl_resources.qc:15
string noise
Definition: progsdefs.qc:209
vector dropped_origin
Definition: world.qh:157
#define IS_CLIENT(v)
Definition: utils.qh:13
int team
Definition: main.qh:157
int team_saved
Definition: vote.qh:68
entity() spawn
float DAMAGE_AIM
Definition: progsdefs.qc:284
float bot_attack
Definition: api.qh:38
const float MOVE_NORMAL
Definition: csprogsdefs.qc:252
vector maxs
Definition: csprogsdefs.qc:113
void SUB_UseTargets(entity this, entity actor, entity trigger)
Definition: triggers.qc:366
float dmg
Definition: platforms.qh:6
spawnfunc(info_player_attacker)
Definition: sv_assault.qc:283
origin
Definition: ent_cs.qc:114
float MOVETYPE_BOUNCE
Definition: progsdefs.qc:256
string mdl_dead
Definition: sv_monsters.qh:57
float effects
Definition: csprogsdefs.qc:111
float spawnflags
Definition: progsdefs.qc:191
#define DMG_NOWEP
Definition: damage.qh:126
entity owner
Definition: main.qh:73
string model
Definition: csprogsdefs.qc:108
IntrusiveList g_initforplayer
Definition: client.qh:373
IntrusiveList g_bot_targets
Definition: api.qh:149
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
Definition: cl_resources.qc:31
vector absmax
Definition: csprogsdefs.qc:92
RES_HEALTH
Definition: ent_cs.qc:126
const float EF_NODRAW
Definition: csprogsdefs.qc:305
const int START_DISABLED
Definition: defs.qh:7
entity msg_entity
Definition: progsdefs.qc:63
float dmg_edge
Definition: damage.qh:38
vector mins
Definition: csprogsdefs.qc:113
float cnt
Definition: powerups.qc:24
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
const int CH_TRIGGER
Definition: sound.qh:12
const int BREAKABLE_NODAMAGE
Definition: breakable.qh:5
string noise1
Definition: progsdefs.qc:209
string message
Definition: powerups.qc:19
#define NULL
Definition: post.qh:17
float dmg_force
Definition: damage.qh:39
float max_health
#define crandom()
Returns a random number between -1.0 and 1.0.
Definition: math.qh:27
const float VOL_BASE
Definition: sound.qh:36
float takedamage
Definition: progsdefs.qc:147
const float ATTEN_NORM
Definition: sound.qh:30
float nextthink
Definition: csprogsdefs.qc:121
const int STATE_BROKEN
Definition: breakable.qh:8
const int CH_TRIGGER_SINGLE
Definition: sound.qh:13
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define tokenize_console
Definition: dpextensions.qh:24
IntrusiveList g_projectiles
Definition: common.qh:46
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
const float SOLID_BSP
Definition: csprogsdefs.qc:248
float pain_finished
float DPCONTENTS_BODY
float count
Definition: powerups.qc:22
const int STATE_ALIVE
Definition: breakable.qh:7
#define _sound(e, c, s, v, a)
Definition: sound.qh:50
float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype,.entity weaponentity, entity directhitentity)
Definition: damage.qc:1057
entity realowner
Definition: common.qh:25
IntrusiveList g_saved_team
Definition: vote.qh:77
setorigin(ent, v)
float dphitcontentsmask
#define setthink(e, f)
void SUB_SetFade(entity ent, float vanish_time, float fading_time)
Definition: subs.qc:77
vector angles
Definition: csprogsdefs.qc:104
#define use
Definition: csprogsdefs.qh:50
float trace_startsolid
Definition: csprogsdefs.qc:35
#define DEATH_ISSPECIAL(t)
Definition: all.qh:35
vector absmin
Definition: csprogsdefs.qc:92
float time
Definition: csprogsdefs.qc:16
float trace_fraction
Definition: csprogsdefs.qc:36
float DAMAGE_NO
Definition: progsdefs.qc:282
void set_movetype(entity this, int mt)
var void func_null()
float solid
Definition: csprogsdefs.qc:99
float DAMAGE_YES
Definition: progsdefs.qc:283