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