1 /*
2 Copyright (C) 2009-2021 Parallel Realities
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19 
20 #include "../headers.h"
21 
22 #include "../audio/audio.h"
23 #include "../collisions.h"
24 #include "../custom_actions.h"
25 #include "../entity.h"
26 #include "../event/global_trigger.h"
27 #include "../event/trigger.h"
28 #include "../graphics/animation.h"
29 #include "../graphics/decoration.h"
30 #include "../hud.h"
31 #include "../item/item.h"
32 #include "../projectile.h"
33 #include "../system/error.h"
34 #include "../system/properties.h"
35 #include "../system/random.h"
36 
37 extern Entity *self, player;
38 
39 static void lookForPlayer(void);
40 static void skullAttackChargeUp(void);
41 static void skullAttack(void);
42 static void skullAttackFinish(void);
43 static void takeDamage(Entity *, int);
44 static void skullShotMove(void);
45 static void touch(Entity *);
46 static void skullShotReflect(Entity *);
47 static void die(void);
48 
addSkullDoor(int x,int y,char * name)49 Entity *addSkullDoor(int x, int y, char *name)
50 {
51 	Entity *e = getFreeEntity();
52 
53 	if (e == NULL)
54 	{
55 		showErrorAndExit("No free slots to add a Skull Door");
56 	}
57 
58 	loadProperties(name, e);
59 
60 	e->x = x;
61 	e->y = y;
62 
63 	e->draw = &drawLoopingAnimationToMap;
64 	e->touch = &touch;
65 	e->die = ¨
66 	e->takeDamage = &takeDamage;
67 
68 	e->action = &lookForPlayer;
69 
70 	e->type = ENEMY;
71 
72 	setEntityAnimation(e, "STAND");
73 
74 	return e;
75 }
76 
lookForPlayer()77 static void lookForPlayer()
78 {
79 	if (player.health > 0)
80 	{
81 		if (collision(self->x + (self->face == LEFT ? -400 : self->w), self->y, 400, self->h, player.x, player.y, player.w, player.h) == 1)
82 		{
83 			setEntityAnimation(self, "CHARGE");
84 
85 			self->animationCallback = &skullAttackChargeUp;
86 		}
87 	}
88 
89 	checkToMap(self);
90 }
91 
skullAttackChargeUp()92 static void skullAttackChargeUp()
93 {
94 	setEntityAnimation(self, "CHARGE_WAIT");
95 
96 	self->thinkTime = 30;
97 
98 	self->action = &skullAttack;
99 }
100 
skullAttack()101 static void skullAttack()
102 {
103 	Entity *e;
104 
105 	self->thinkTime--;
106 
107 	if (self->thinkTime <= 0)
108 	{
109 		setEntityAnimation(self, "ATTACK");
110 
111 		e = addProjectile("enemy/skull_shot", self, self->x, self->y, self->face == LEFT ? -6 : 6, 0);
112 
113 		playSoundToMap("sound/boss/snake_boss/snake_boss_shot", -1, self->x, self->y, 0);
114 
115 		if (self->face == LEFT)
116 		{
117 			e->x = self->x + self->w - e->w - self->offsetX;
118 		}
119 
120 		else
121 		{
122 			e->x = self->x + self->offsetX;
123 		}
124 
125 		e->y = self->y + self->offsetY;
126 
127 		e->face = self->face;
128 
129 		e->action = &skullShotMove;
130 
131 		e->flags |= FLY;
132 
133 		e->reactToBlock = &skullShotReflect;
134 
135 		e->thinkTime = 1200;
136 
137 		e->mental = 2;
138 
139 		self->thinkTime = 60;
140 
141 		self->action = &skullAttackFinish;
142 	}
143 
144 	checkToMap(self);
145 }
146 
skullAttackFinish()147 static void skullAttackFinish()
148 {
149 	self->thinkTime--;
150 
151 	if (self->thinkTime <= 0)
152 	{
153 		setEntityAnimation(self, "STAND");
154 
155 		self->action = &lookForPlayer;
156 	}
157 
158 	checkToMap(self);
159 }
160 
skullShotMove()161 static void skullShotMove()
162 {
163 	Entity *e;
164 
165 	self->dirX = self->face == LEFT ? -fabs(self->dirX) : fabs(self->dirX);
166 
167 	self->mental--;
168 
169 	if (self->mental <= 0)
170 	{
171 		e = addBasicDecoration(self->x, self->y, "decoration/skull_trail");
172 
173 		if (e != NULL)
174 		{
175 			e->x = self->face == LEFT ? self->x + self->w - e->w : self->x;
176 
177 			e->y = self->y + self->h / 2 - e->h / 2;
178 
179 			e->y += (prand() % 8) * (prand() % 2 == 0 ? 1 : -1);
180 
181 			e->thinkTime = 15 + prand() % 15;
182 
183 			e->dirY = (1 + prand() % 10) * (prand() % 2 == 0 ? 1 : -1);
184 
185 			e->dirY /= 10;
186 		}
187 
188 		self->mental = 2;
189 	}
190 
191 	checkToMap(self);
192 
193 	if (self->dirX == 0 || self->thinkTime <= 0)
194 	{
195 		self->inUse = FALSE;
196 	}
197 }
198 
skullShotReflect(Entity * other)199 static void skullShotReflect(Entity *other)
200 {
201 	if (other->element != PHANTASMAL)
202 	{
203 		self->inUse = FALSE;
204 
205 		return;
206 	}
207 
208 	if (other->mental <= 7)
209 	{
210 		self->damage = 50;
211 	}
212 
213 	else if (other->mental <= 15)
214 	{
215 		self->damage = 30;
216 	}
217 
218 	else if (other->mental <= 30)
219 	{
220 		self->damage = 20;
221 	}
222 
223 	else
224 	{
225 		self->damage = 5;
226 	}
227 
228 	self->parent = other;
229 
230 	self->face = self->face == LEFT ? RIGHT : LEFT;
231 }
232 
takeDamage(Entity * other,int damage)233 static void takeDamage(Entity *other, int damage)
234 {
235 	Entity *temp;
236 
237 	if (self->flags & INVULNERABLE)
238 	{
239 		return;
240 	}
241 
242 	if (other->element == PHANTASMAL)
243 	{
244 		entityTakeDamageNoFlinch(other, damage);
245 	}
246 
247 	else
248 	{
249 		playSoundToMap("sound/common/dink", -1, self->x, self->y, 0);
250 
251 		setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
252 
253 		if (other->reactToBlock != NULL)
254 		{
255 			temp = self;
256 
257 			self = other;
258 
259 			self->reactToBlock(temp);
260 
261 			self = temp;
262 		}
263 
264 		if (other->type != PROJECTILE && prand() % 10 == 0)
265 		{
266 			setInfoBoxMessage(60, 255, 255, 255, _("This weapon is not having any effect..."));
267 		}
268 
269 		damage = 0;
270 	}
271 }
272 
die()273 static void die()
274 {
275 	int i;
276 	Entity *e;
277 	char name[MAX_VALUE_LENGTH];
278 
279 	playSoundToMap("sound/common/crumble", -1, self->x, self->y, 0);
280 
281 	SNPRINTF(name, sizeof(name), "%s_piece", self->name);
282 
283 	fireTrigger(self->objectiveName);
284 
285 	fireGlobalTrigger(self->objectiveName);
286 
287 	for (i=0;i<9;i++)
288 	{
289 		e = addTemporaryItem(name, self->x, self->y, self->face, 0, 0);
290 
291 		e->x += (self->w - e->w) / 2;
292 		e->y += (self->w - e->w) / 2;
293 
294 		e->dirX = (prand() % 5) * (prand() % 2 == 0 ? -1 : 1);
295 		e->dirY = ITEM_JUMP_HEIGHT + (prand() % ITEM_JUMP_HEIGHT);
296 
297 		setEntityAnimationByID(e, i);
298 
299 		e->thinkTime = 60 + (prand() % 180);
300 	}
301 
302 	self->damage = 0;
303 
304 	if (!(self->flags & INVULNERABLE))
305 	{
306 		self->touch = &entityTouch;
307 
308 		self->flags &= ~FLY;
309 
310 		self->flags |= (DO_NOT_PERSIST|NO_DRAW);
311 
312 		self->thinkTime = 60;
313 
314 		setCustomAction(self, &invulnerableNoFlash, 240, 0, 0);
315 
316 		self->frameSpeed = 0;
317 
318 		self->action = &standardDie;
319 
320 		self->damage = 0;
321 	}
322 }
323 
touch(Entity * other)324 static void touch(Entity *other)
325 {
326 	if (!(other->parent != NULL && other->parent == self))
327 	{
328 		if (other->flags & ATTACKING)
329 		{
330 			takeDamage(other, other->damage);
331 		}
332 
333 		if (self->inUse == TRUE && self->touch != NULL)
334 		{
335 			pushEntity(other);
336 		}
337 	}
338 }
339