1 /**
2 HitCheck.c
3 Effect for hit checking.
4 Facilitates any hit check of a projectile. The Projectile hits anything
5 which is either alive or returns for IsProjectileTarget(object projectile,
6 object shooter) true. If the projectile hits something, it calls
7 HitObject(object target) in the projectile.
8
9 @author Newton, Boni
10 */
11
FxHitCheckStart(object target,proplist effect,int temp,object by_obj,bool never_shooter)12 global func FxHitCheckStart(object target, proplist effect, int temp, object by_obj, bool never_shooter)
13 {
14 if (temp)
15 return;
16 effect.x = target->GetX();
17 effect.y = target->GetY();
18 if (!by_obj || GetType(by_obj) != C4V_C4Object)
19 by_obj = target;
20 if (by_obj->Contained())
21 by_obj = by_obj->Contained();
22 effect.shooter = by_obj;
23 effect.live = false;
24 effect.never_shooter = never_shooter;
25
26 // C4D_Object has a hitcheck too -> change to vehicle to supress that.
27 if (target->GetCategory() & C4D_Object)
28 target->SetCategory((target->GetCategory() - C4D_Object) | C4D_Vehicle);
29 return;
30 }
31
FxHitCheckStop(object target,proplist effect,int reason,bool temp)32 global func FxHitCheckStop(object target, proplist effect, int reason, bool temp)
33 {
34 if (temp)
35 return;
36
37 target->SetCategory(target->GetID()->GetCategory());
38 return;
39 }
40
FxHitCheckDoCheck(object target,proplist effect)41 global func FxHitCheckDoCheck(object target, proplist effect)
42 {
43 var obj;
44 // rather search in front of the projectile, since a hit might delete the effect,
45 // and clonks can effectively hide in front of walls.
46 var oldx = target->GetX();
47 var oldy = target->GetY();
48 var newx = target->GetX() + target->GetXDir() / 10;
49 var newy = target->GetY() + target->GetYDir() / 10;
50 var dist = Distance(oldx, oldy, newx, newy);
51
52 var shooter = effect.shooter;
53 var live = effect.live;
54
55 if (live)
56 shooter = target;
57
58 if (dist <= Max(1, Max(Abs(target->GetXDir()), Abs(target->GetYDir()))) * 2)
59 {
60 // We search for objects along the line on which we moved since the last check
61 // and sort by distance (closer first).
62 for (obj in FindObjects(Find_OnLine(oldx, oldy, newx, newy),
63 Find_NoContainer(),
64 Find_Layer(target->GetObjectLayer()),
65 Find_PathFree(target),
66 Sort_Distance(oldx, oldy)))
67 {
68 // Excludes
69 if (!obj) continue; // hit callback of one object might have removed other objects
70 if(obj == target) continue;
71 if(obj == shooter) continue;
72
73 // Unlike in hazard, there is no NOFF rule (yet)
74 // CheckEnemy
75 //if(!CheckEnemy(obj,target)) continue;
76
77 // IsProjectileTarget will be hit (defaults to true for OCF_Alive).
78 if (obj->~IsProjectileTarget(target, shooter))
79 {
80 target->~HitObject(obj);
81 if (!target)
82 return;
83 }
84 }
85 }
86 return;
87 }
88
FxHitCheckEffect(string newname)89 global func FxHitCheckEffect(string newname)
90 {
91 if (newname == "HitCheck")
92 return -2;
93 return;
94 }
95
FxHitCheckAdd(object target,proplist effect,string neweffectname,int newtimer,by_obj,never_shooter)96 global func FxHitCheckAdd(object target, proplist effect, string neweffectname, int newtimer, by_obj, never_shooter)
97 {
98 effect.x = target->GetX();
99 effect.y = target->GetY();
100 if (!by_obj)
101 by_obj = target;
102 if (by_obj->Contained())
103 by_obj = by_obj->Contained();
104 effect.shooter = by_obj;
105 effect.live = false;
106 effect.never_shooter = never_shooter;
107 return;
108 }
109
FxHitCheckTimer(object target,proplist effect,int time)110 global func FxHitCheckTimer(object target, proplist effect, int time)
111 {
112 EffectCall(target, effect, "DoCheck");
113 // It could be that it hit something and removed itself. thus check if target is still there.
114 // The effect will be deleted right after this.
115 if (!target)
116 return -1;
117
118 effect.x = target->GetX();
119 effect.y = target->GetY();
120 var live = effect.live;
121 var never_shooter = effect.never_shooter;
122 var shooter = effect.shooter;
123
124 // The projectile will be only switched to "live", meaning that it can hit the
125 // shooter himself when the shot exited the shape of the shooter one time.
126 if (!never_shooter)
127 {
128 if (!live)
129 {
130 var ready = true;
131 // We search for all objects with the id of our shooter.
132 if (shooter)
133 {
134 if (FindObject(Find_AtPoint(target->GetX(), target->GetY()), Find_InArray([shooter])))
135 {
136 // we may not switch to "live" yet.
137 ready = false;
138 }
139 }
140 // Otherwise, the shot will be live.
141 if (ready)
142 effect.live = true;
143 }
144 }
145 return;
146 }
147
IsProjectileTarget(object projectile,object shooter)148 global func IsProjectileTarget(object projectile, object shooter)
149 {
150 return GetOCF() & OCF_Alive;
151 }
152
153 /*
154 Checks whether an object is ready to take damage from this object, calling QueryCatchBlow.
155 */
WeaponCanHit(object target)156 global func WeaponCanHit(object target)
157 {
158 if (target->~QueryCatchBlow(this)) return false;
159 if (!target || !this) return false;
160 return true;
161 }
162
163 /*
164 Deals damage to an object, draining either energy for living things or dealing damage otherwise.
165 CatchBlow is called on the target if it's alive.
166 */
WeaponDamage(object target,int damage,int damage_type,bool exact_damage)167 global func WeaponDamage(object target, int damage, int damage_type, bool exact_damage)
168 {
169 if (!this || !target) return;
170
171 damage_type = damage_type ?? FX_Call_EngObjHit;
172 var true_damage = damage;
173 if (exact_damage) true_damage = damage / 1000;
174
175 if (target->GetAlive())
176 {
177 target->DoEnergy(-damage, exact_damage, damage_type, GetController());
178 if (!target) return;
179
180 target->~CatchBlow(-true_damage, this);
181 }
182 else
183 {
184 target->DoDamage(true_damage, damage_type, GetController());
185 }
186 }
187
188 /*
189 Tumbles an object based on this object's speed and mass.
190 strength = 100 means using 100% of the own mass for tumbling the other object.
191 */
WeaponTumble(object target,int strength)192 global func WeaponTumble(object target, int strength)
193 {
194 if (!this || !target) return;
195
196 strength = strength ?? 100;
197 if (strength <= 0) return;
198
199 if (target->GetAlive())
200 {
201 target->SetAction("Tumble");
202 // Constrained by conservation of linear momentum, unrealism != 1 for unrealistic behaviour.
203 var unrealism = 3;
204 var mass = strength * GetMass() / 100;
205 var obj_mass = target->GetMass();
206 target->SetXDir((target->GetXDir() * obj_mass + GetXDir() * mass * unrealism) / (mass + obj_mass));
207 target->SetYDir((target->GetYDir() * obj_mass + GetYDir() * mass * unrealism) / (mass + obj_mass));
208 }
209 }
210