1 /*
2     C-Dogs SDL
3     A port of the legendary (and fun) action/arcade cdogs.
4     Copyright (c) 2013-2016, 2018-2019 Cong Xu
5     All rights reserved.
6 
7     Redistribution and use in source and binary forms, with or without
8     modification, are permitted provided that the following conditions are met:
9 
10     Redistributions of source code must retain the above copyright notice, this
11     list of conditions and the following disclaimer.
12     Redistributions in binary form must reproduce the above copyright notice,
13     this list of conditions and the following disclaimer in the documentation
14     and/or other materials provided with the distribution.
15 
16     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19     ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26     POSSIBILITY OF SUCH DAMAGE.
27 */
28 #include "cpic.h"
29 
30 #include "blit.h"
31 #include "json_utils.h"
32 #include "log.h"
33 #include "palette.h"
34 #include "pic_manager.h"
35 #include "utils.h"
36 
37 
StrPicType(const char * s)38 PicType StrPicType(const char *s)
39 {
40 	S2T(PICTYPE_NORMAL, "Normal");
41 	S2T(PICTYPE_DIRECTIONAL, "Directional");
42 	S2T(PICTYPE_ANIMATED, "Animated");
43 	S2T(PICTYPE_ANIMATED_RANDOM, "AnimatedRandom");
44 	CASSERT(false, "unknown pic type");
45 	return PICTYPE_NORMAL;
46 }
47 
48 
CPicDrawContextNew(void)49 CPicDrawContext CPicDrawContextNew(void)
50 {
51 	CPicDrawContext c;
52 	c.Dir = DIRECTION_UP;
53 	c.Offset = svec2i_zero();
54 	c.Radians = 0;
55 	c.Scale = svec2_one();
56 	c.Mask = colorWhite;
57 	c.Flip = SDL_FLIP_NONE;
58 	return c;
59 }
60 
61 
NamedPicFree(NamedPic * n)62 void NamedPicFree(NamedPic *n)
63 {
64 	CFREE(n->name);
65 	PicFree(&n->pic);
66 }
67 
68 
NamedSpritesInit(NamedSprites * ns,const char * name)69 void NamedSpritesInit(NamedSprites *ns, const char *name)
70 {
71 	CSTRDUP(ns->name, name);
72 	CArrayInit(&ns->pics, sizeof(Pic));
73 }
NamedSpritesFree(NamedSprites * ns)74 void NamedSpritesFree(NamedSprites *ns)
75 {
76 	if (ns == NULL)
77 	{
78 		return;
79 	}
80 	CFREE(ns->name);
81 	for (int i = 0; i < (int)ns->pics.size; i++)
82 	{
83 		PicFree(CArrayGet(&ns->pics, i));
84 	}
85 	CArrayTerminate(&ns->pics);
86 }
87 
88 static void LoadNormal(CPic *p, const json_t *node);
89 static void LoadMaskTint(CPic *p, json_t *node);
90 
CPicLoadJSON(CPic * p,json_t * node)91 void CPicLoadJSON(CPic *p, json_t *node)
92 {
93 	char *tmp = GetString(node, "Type");
94 	p->Type = StrPicType(tmp);
95 	CFREE(tmp);
96 	tmp = NULL;
97 	switch (p->Type)
98 	{
99 	case PICTYPE_NORMAL:
100 		LoadNormal(p, json_find_first_label(node, "Pic")->child);
101 		break;
102 	case PICTYPE_DIRECTIONAL:
103 		LoadStr(&tmp, node, "Sprites");
104 		if (tmp == NULL)
105 		{
106 			LOG(LM_GFX, LL_ERROR, "cannot load sprites");
107 			goto bail;
108 		}
109 		p->u.Sprites = &PicManagerGetSprites(&gPicManager, tmp)->pics;
110 		CFREE(tmp);
111 		break;
112 	case PICTYPE_ANIMATED:	// fallthrough
113 	case PICTYPE_ANIMATED_RANDOM:
114 		LoadStr(&tmp, node, "Sprites");
115 		if (tmp == NULL)
116 		{
117 			LOG(LM_GFX, LL_ERROR, "cannot load sprites");
118 			goto bail;
119 		}
120 		p->u.Animated.Sprites =
121 			&PicManagerGetSprites(&gPicManager, tmp)->pics;
122 		CFREE(tmp);
123 		LoadInt(&p->u.Animated.Count, node, "Count");
124 		LoadInt(&p->u.Animated.TicksPerFrame, node, "TicksPerFrame");
125 		p->u.Animated.TicksPerFrame = MAX(p->u.Animated.TicksPerFrame, 0);
126 		break;
127 	default:
128 		CASSERT(false, "unknown pic type");
129 		break;
130 	}
131 	LoadMaskTint(p, node);
132 bail:
133 	// TODO: return error
134 	return;
135 }
136 
CPicInitNormal(CPic * p,const Pic * pic)137 void CPicInitNormal(CPic *p, const Pic *pic)
138 {
139 	p->Type = PICTYPE_NORMAL;
140 	p->u.Pic = pic;
141 	p->Mask = colorWhite;
142 }
CPicInitNormalFromName(CPic * p,const char * name)143 void CPicInitNormalFromName(CPic *p, const char *name)
144 {
145 	p->Type = PICTYPE_NORMAL;
146 	p->u.Pic = PicManagerGetPic(&gPicManager, name);
147 	p->Mask = colorWhite;
148 }
149 
CPicLoadNormal(CPic * p,const json_t * node)150 void CPicLoadNormal(CPic *p, const json_t *node)
151 {
152 	p->Type = PICTYPE_NORMAL;
153 	LoadNormal(p, node);
154 	p->Mask = colorWhite;
155 }
156 
LoadNormal(CPic * p,const json_t * node)157 static void LoadNormal(CPic *p, const json_t *node)
158 {
159 	char *tmp = json_unescape(node->text);
160 	p->u.Pic = PicManagerGetPic(&gPicManager, tmp);
161 	CFREE(tmp);
162 }
163 
LoadMaskTint(CPic * p,json_t * node)164 static void LoadMaskTint(CPic *p, json_t *node)
165 {
166 	p->Mask = colorWhite;
167 	if (json_find_first_label(node, "Mask"))
168 	{
169 		char *tmp = GetString(node, "Mask");
170 		p->Mask = StrColor(tmp);
171 		CFREE(tmp);
172 	}
173 	else if (json_find_first_label(node, "Tint"))
174 	{
175 		// TODO: create new pic, as tinting does not work correctly using mask
176 		// Mask only darkens, whereas tinting can change hue without affecting
177 		// value, for example
178 		json_t *tint = json_find_first_label(node, "Tint")->child->child;
179 		HSV hsv;
180 		hsv.h = atof(tint->text);
181 		tint = tint->next;
182 		hsv.s = atof(tint->text);
183 		tint = tint->next;
184 		hsv.v = atof(tint->text);
185 		p->Mask = ColorTint(colorWhite, hsv);
186 		p->Mask.a = 0x40;
187 	}
188 }
189 
CPicIsLoaded(const CPic * p)190 bool CPicIsLoaded(const CPic *p)
191 {
192 	switch (p->Type)
193 	{
194 	case PICTYPE_NORMAL:
195 		return p->u.Pic != NULL;
196 	case PICTYPE_DIRECTIONAL:
197 		return p->u.Sprites != NULL;
198 	case PICTYPE_ANIMATED:
199 	case PICTYPE_ANIMATED_RANDOM:
200 		return p->u.Animated.Sprites != NULL;
201 	default:
202 		CASSERT(false, "unknown pic type");
203 		return false;
204 	}
205 }
206 
CPicGetSize(const CPic * p)207 struct vec2i CPicGetSize(const CPic *p)
208 {
209 	const Pic *pic = CPicGetPic(p, 0);
210 	if (pic == NULL)
211 	{
212 		return svec2i_zero();
213 	}
214 	return pic->size;
215 }
216 
CPicCopyPic(CPic * dest,const CPic * src)217 void CPicCopyPic(CPic *dest, const CPic *src)
218 {
219 	memcpy(dest, src, sizeof *src);
220 	if (dest->Type == PICTYPE_ANIMATED_RANDOM)
221 	{
222 		// initialise frame with a random value
223 		dest->u.Animated.Frame = rand() % (int)dest->u.Animated.Sprites->size;
224 	}
225 }
226 
CPicUpdate(CPic * p,const int ticks)227 void CPicUpdate(CPic *p, const int ticks)
228 {
229 	switch (p->Type)
230 	{
231 	case PICTYPE_ANIMATED:
232 		{
233 			p->u.Animated.Count += ticks;
234 			if (p->u.Animated.TicksPerFrame > 0)
235 			{
236 				while (p->u.Animated.Count >= p->u.Animated.TicksPerFrame)
237 				{
238 					p->u.Animated.Frame++;
239 					p->u.Animated.Count -= p->u.Animated.TicksPerFrame;
240 				}
241 				while (p->u.Animated.Frame >= (int)p->u.Animated.Sprites->size)
242 				{
243 					p->u.Animated.Frame -= (int)p->u.Animated.Sprites->size;
244 				}
245 			}
246 		}
247 		break;
248 	case PICTYPE_ANIMATED_RANDOM:
249 		if (p->u.Animated.Count == 0)
250 		{
251 			// Initial frame
252 			p->u.Animated.Frame = rand() % (int)p->u.Animated.Sprites->size;
253 		}
254 		p->u.Animated.Count += ticks;
255 		if (p->u.Animated.TicksPerFrame > 0 &&
256 			p->u.Animated.Count >= p->u.Animated.TicksPerFrame)
257 		{
258 			p->u.Animated.Frame = rand() % (int)p->u.Animated.Sprites->size;
259 			p->u.Animated.Count = 0;
260 		}
261 		break;
262 	default:
263 		// Do nothing
264 		break;
265 	}
266 }
CPicGetPic(const CPic * p,const int idx)267 const Pic *CPicGetPic(const CPic *p, const int idx)
268 {
269 	switch (p->Type)
270 	{
271 	case PICTYPE_NORMAL:
272 		return p->u.Pic;
273 	case PICTYPE_DIRECTIONAL:
274 		return p->u.Sprites != NULL ? CArrayGet(p->u.Sprites, idx) : NULL;
275 	case PICTYPE_ANIMATED:
276 	case PICTYPE_ANIMATED_RANDOM:
277 		if (p->u.Animated.Frame < 0 ||
278 			p->u.Animated.Frame >= (int)p->u.Animated.Sprites->size)
279 		{
280 			return NULL;
281 		}
282 		return CArrayGet(p->u.Animated.Sprites, p->u.Animated.Frame);
283 	default:
284 		CASSERT(false, "unknown pic type");
285 		return NULL;
286 	}
287 }
CPicDraw(GraphicsDevice * g,const CPic * p,const struct vec2i pos,const CPicDrawContext * context)288 void CPicDraw(
289 	GraphicsDevice *g, const CPic *p,
290 	const struct vec2i pos, const CPicDrawContext *context)
291 {
292 	CPicDrawContext ctx;
293 	if (context == NULL)
294 	{
295 		ctx = CPicDrawContextNew();
296 		context = &ctx;
297 	}
298 	const Pic *pic = CPicGetPic(p, context->Dir);
299 	if (pic == NULL)
300 	{
301 		return;
302 	}
303 	const struct vec2i picPos = svec2i_add(pos, context->Offset);
304 	PicRender(
305 		pic, g->gameWindow.renderer, picPos, ColorMult(p->Mask, context->Mask),
306 		context->Radians,
307 		context->Scale, context->Flip, Rect2iZero());
308 }
309