/
animate.c
433 lines (377 loc) · 12.3 KB
/
animate.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
// Meridian 59, Copyright 1994-2012 Andrew Kirmse and Chris Kirmse.
// All rights reserved.
//
// This software is distributed under a license that is described in
// the LICENSE file that accompanies it.
//
// Meridian is a registered trademark.
/*
* animate.c
*
* This file contains functions for animating objects.
*
* Normally, animation is performed every time we poll the keyboard (i.e. whenever
* the main window has no events in its queue). This produces animation as fast as the
* machine is capable. However, when a menu or dialog is up, the main window's loop
* is completely bypassed. To keep animating during these times, we start an animation
* timer at a constant rate (as fast as possible). When the menu or dialog exits, we
* kill the timer and start polling again.
*
* Each object has a set of bitmaps associated with it. These bitmaps are
* divided into groups; for a given object, only one group is active at a time.
* Animation simply cycles through the groups for each animated object.
*
* Animation also produces smooth motion out of position changes sent by the server.
* This is done in move.c.
*/
#include "client.h"
#define ANIMATE_INTERVAL 165 // ms between background animation updates
#define FLICKER_LEVEL (LIGHT_LEVELS/2)
#define FLASH_LEVEL (LIGHT_LEVELS/2)
#define TIME_FLASH 1000
static int animation_timer = 0; // id of animation timer, or 0 if none
static DWORD timeLastFrame;
#define TIME_FULL_OBJECT_PHASE 1800
static int phaseStates[] = {
OF_DRAW_PLAIN,OF_TRANSLUCENT75,OF_TRANSLUCENT50,OF_TRANSLUCENT25,OF_INVISIBLE,
OF_INVISIBLE,OF_INVISIBLE,
OF_TRANSLUCENT25,OF_TRANSLUCENT50,OF_TRANSLUCENT75,OF_DRAW_PLAIN};
static int numPhases = sizeof(phaseStates) / sizeof(int);
extern room_type current_room;
extern player_info player;
/* local function prototypes */
static Bool AnimateObjects(int dt);
static Bool AnimateGrids(int dt);
static Bool AnimateProjectiles(int dt);
static Bool AnimateBackgroundOverlays(int dt);
static Bool AnimatePlayerOverlays(int dt);
static void AnimationResetSingle(Animate *a);
/************************************************************************/
void AnimationTimerStart(void)
{
// See if already started
if (animation_timer != 0)
return;
animation_timer = SetTimer(hMain, TIMER_ANIMATE, ANIMATE_INTERVAL, NULL);
if (animation_timer == 0)
ClientError(hInst, hMain, IDS_NOTIMERS);
}
/************************************************************************/
void AnimationTimerAbort(void)
{
if (animation_timer != 0)
{
KillTimer(hMain, TIMER_ANIMATE);
animation_timer = 0;
}
}
/************************************************************************/
void AnimationSleep(void)
{
Sleep(ANIMATE_INTERVAL);
}
DWORD GetFrameTime(void)
{
return timeLastFrame;
}
/************************************************************************/
void AnimationTimerProc(HWND hwnd, UINT timer)
{
Bool need_redraw = False;
static DWORD last_animate_time = 0;
DWORD dt, now;
PingTimerProc(hwnd, 0, 0, 0);
if (!(GameGetState() == GAME_PLAY || GameGetState() == GAME_SELECT))
return;
if (last_animate_time == 0)
{
last_animate_time = timeGetTime();
return;
}
config.quickstart = FALSE;
now = timeGetTime();
dt = now - last_animate_time;
last_animate_time = now;
timeLastFrame = dt;
/* Send event to modules */
ModuleEvent(EVENT_ANIMATE, dt);
/* Send event to non-module child windows */
if (config.animate)
{
Lagbox_Animate(dt);
}
/* Animate the first-person view elements */
if (config.animate && GetGameDataValid())
{
// Avoid short-circuiting OR
need_redraw |= ObjectsMove(dt);
need_redraw |= ProjectilesMove(dt);
need_redraw |= AnimateObjects(dt);
need_redraw |= AnimateRoom(¤t_room, dt);
need_redraw |= AnimateProjectiles(dt);
need_redraw |= AnimatePlayerOverlays(dt);
need_redraw |= AnimateBackgroundOverlays(dt);
AnimateDescription(dt);
need_redraw |= AnimateEffects(dt);
if (need_redraw)
RedrawAll();
}
if (GetGameDataValid())
RedrawForce();
return;
}
/************************************************************************/
/*
* Animate objects in current room; return True if any was animated.
* dt is number of milliseconds since last time animation timer went off.
*/
Bool AnimateObjects(int dt)
{
Bool need_redraw = False;
list_type l;
for (l = current_room.contents; l != NULL; l = l->next)
{
room_contents_node *r = (room_contents_node *) (l->data);
int old_animate = r->obj.animate->animation;
need_redraw |= AnimateObject(&r->obj, dt);
// If room object animation finished, restore motion animation if appropriate.
// (Done after drawing so that last frame of animation not skipped).
if (r->obj.animate->animation == ANIMATE_NONE && old_animate != ANIMATE_NONE && r->moving)
{
RoomObjectSetAnimation(r, True);
r->motion.move_animating = True;
}
}
return need_redraw;
}
/************************************************************************/
/*
* AnimateObject: Animate a single object; return True iff animated.
* dt is number of milliseconds since last time animation timer went off.
*/
Bool AnimateObject(object_node *obj, int dt)
{
Bool need_redraw = False;
list_type over_list;
if (OF_FLICKERING == (OF_BOUNCING & obj->flags))
{
obj->lightAdjust = rand() % FLICKER_LEVEL;
need_redraw = TRUE;
}
if (OF_FLASHING == (OF_BOUNCING & obj->flags))
{
DWORD angleFlash;
obj->bounceTime += min(dt,50);
if (obj->bounceTime > TIME_FLASH)
obj->bounceTime -= TIME_FLASH;
angleFlash = NUMDEGREES * obj->bounceTime / TIME_FLASH;
obj->lightAdjust = FIXED_TO_INT(fpMul(FLASH_LEVEL, SIN(angleFlash)));
need_redraw = TRUE;
}
if (obj->animate->animation != ANIMATE_NONE)
{
object_bitmap_type obj_bmap;
obj_bmap = FindObjectBitmap(obj->icon_res);
if (obj_bmap != NULL)
need_redraw |= AnimateSingle(obj->animate, BitmapsNumGroups(obj_bmap->bmaps), dt);
}
if (OF_PHASING == (OF_PHASING & obj->flags))
{
int anglePhase;
obj->phaseTime += min(dt,40);
if (obj->phaseTime > TIME_FULL_OBJECT_PHASE)
obj->phaseTime -= TIME_FULL_OBJECT_PHASE;
anglePhase = numPhases * obj->phaseTime / TIME_FULL_OBJECT_PHASE;
obj->flags = (~OF_EFFECT_MASK & obj->flags) | phaseStates[anglePhase];
need_redraw = TRUE;
}
// Animate object's overlays
for (over_list = *(obj->overlays); over_list != NULL; over_list = over_list->next)
{
object_bitmap_type obj_bmap;
Overlay *overlay = (Overlay *) (over_list->data);
if (overlay->animate.animation == ANIMATE_NONE)
continue;
obj_bmap = FindObjectBitmap(overlay->icon_res);
if (obj_bmap != NULL)
{
need_redraw |= AnimateSingle(&overlay->animate, BitmapsNumGroups(obj_bmap->bmaps), dt);
}
}
return need_redraw;
}
/************************************************************************/
/*
* Animate projectiles active in in current room; return True if any was animated.
* dt is number of milliseconds since last time animation timer went off.
*/
Bool AnimateProjectiles(int dt)
{
object_bitmap_type obj;
list_type l;
Projectile *p;
Bool need_redraw = False, retval;
for (l = current_room.projectiles; l != NULL; l = l->next)
{
p = (Projectile *) (l->data);
if (p->animate.animation == ANIMATE_NONE)
continue;
obj = FindObjectBitmap(p->icon_res);
if (obj != NULL)
{
retval = AnimateSingle(&p->animate, BitmapsNumGroups(obj->bmaps), dt);
need_redraw = need_redraw || retval;
}
}
return need_redraw;
}
/************************************************************************/
/*
* Animate background overlays in current room; return True if any was animated.
* dt is number of milliseconds since last time animation timer went off.
*/
Bool AnimateBackgroundOverlays(int dt)
{
Overlay *overlay;
list_type l;
Bool need_redraw = False, retval;
object_bitmap_type obj_bmap;
for (l = current_room.bg_overlays; l != NULL; l = l->next)
{
overlay = (Overlay *) (l->data);
if (overlay->animate.animation == ANIMATE_NONE)
continue;
obj_bmap = FindObjectBitmap(overlay->icon_res);
if (obj_bmap != NULL)
{
retval = AnimateSingle(&overlay->animate, BitmapsNumGroups(obj_bmap->bmaps), dt);
need_redraw = need_redraw || retval;
}
}
return need_redraw;
}
/************************************************************************/
/*
* Animate player overlays; return True if any was animated.
* dt is number of milliseconds since last time animation timer went off.
*/
Bool AnimatePlayerOverlays(int dt)
{
int i;
Bool need_redraw = False, retval;
for (i=0; i < NUM_PLAYER_OVERLAYS; i++)
{
PlayerOverlay *poverlay = &player.poverlays[i];
if (poverlay->obj == NULL || poverlay->hotspot == 0)
continue;
retval = AnimateObject(poverlay->obj, dt);
need_redraw = need_redraw || retval;
// If animation is over, group becomes -1 => we should remove overlay
if (poverlay->obj->animate->group == (WORD) -1)
poverlay->hotspot = 0;
}
return need_redraw;
}
/************************************************************************/
/*
* AnimateSingle: Animate the given animation structure for a PDIB with the
* given number of groups. Return True iff the display bitmap changes.
* dt is number of milliseconds since last time animation timer went off.
* If num_groups is 0, act as if the PDIB has an infinite number of groups.
*/
Bool AnimateSingle(Animate *a, int num_groups, int dt)
{
Bool need_redraw = False;
switch (a->animation)
{
case ANIMATE_NONE:
break;
case ANIMATE_CYCLE:
// See if it's time to change bitmap
a->tick = a->tick - dt;
if (a->tick > 0)
break;
// Look for special case of cycling through ALL bitmaps
if (a->group_low == a->group_high)
if (num_groups == 0)
a->group++;
else a->group = (a->group + 1) % num_groups;
else a->group = a->group_low +
(a->group - a->group_low + 1) % (a->group_high - a->group_low + 1);
// Reset object timer
a->tick = a->period;
need_redraw = True;
break;
case ANIMATE_ONCE:
// See if it's time to change bitmap
a->tick = a->tick - dt;
if (a->tick > 0)
break;
if (a->group == a->group_high)
{
a->animation = ANIMATE_NONE;
a->group = a->group_final;
}
else a->group++;
// Reset object timer
a->tick = a->period;
need_redraw = True;
break;
default:
debug(("Unknown animation type %d in AnimateSingle\n", a->animation));
break;
}
if (a->group < 0 || (num_groups > 0 && a->group >= num_groups))
{
debug(("Animation produced out of bounds bitmap group %d\n", a->group));
// Don't fix it up; player overlays rely on group going negative to signal end
// a->group = 0;
}
return need_redraw;
}
/************************************************************************/
/*
* VerifyAnimation: See if given animation should be performed, given whether
* animation is currently on. If animation should be performed, return True
* (this may modify given animation structure if a modified animation should
* be performed). If animation shouldn't be done at all, return False.
*/
Bool VerifyAnimation(Animate *a)
{
if (config.animate)
return True;
switch (a->animation)
{
case ANIMATE_NONE:
return True;
case ANIMATE_CYCLE:
return False;
case ANIMATE_ONCE:
// Skip right to end frame of animation
a->animation = ANIMATE_NONE;
a->group = a->group_final;
return True;
default:
debug(("Unknown animation type %d in VerifyAnimation\n", a->animation));
return False;
}
}
/************************************************************************/
/*
* AnimateStop: Stop given animation.
*/
void AnimateStop(Animate *a)
{
// Stop cycle and one-time animations; leave others alone
switch (a->animation)
{
case ANIMATE_CYCLE:
a->animation = ANIMATE_NONE;
a->group = 0;
break;
case ANIMATE_ONCE:
a->animation = ANIMATE_NONE;
a->group = a->group_final;
break;
}
}