319 lines
10 KiB
C
319 lines
10 KiB
C
/*
|
|
* static char *rcsid_anim_c =
|
|
* "$Id: anim.c 11578 2009-02-23 22:02:27Z lalo $";
|
|
*/
|
|
|
|
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2002-2003 Mark Wedel & Crossfire Development Team
|
|
Copyright (C) 1992 Frank Tore Johansen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
The authors can be reached via e-mail at crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* @file anim.c
|
|
* This file contains animation-related code.
|
|
**/
|
|
|
|
#include <global.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
/**
|
|
* Clears all animation-related memory.
|
|
**/
|
|
void free_all_anim(void) {
|
|
int i;
|
|
|
|
for (i = 0; i <= num_animations; i++) {
|
|
free_string(animations[i].name);
|
|
free(animations[i].faces);
|
|
}
|
|
free(animations);
|
|
}
|
|
|
|
/**
|
|
* Loads the lib/animations file.
|
|
* Can be called multiple times without ill effects.
|
|
**/
|
|
void init_anim(void) {
|
|
char buf[MAX_BUF];
|
|
FILE *fp;
|
|
int num_frames = 0, faces[MAX_ANIMATIONS], i;
|
|
|
|
animations_allocated = 9;
|
|
num_animations = 0;
|
|
/* Make a default. New animations start at one, so if something
|
|
* thinks it is animated but hasn't set the animation_id properly,
|
|
* it will have a default value that should be pretty obvious.
|
|
*/
|
|
animations = malloc(10*sizeof(Animations));
|
|
/* set the name so we don't try to dereferance null.
|
|
* Put # at start so it will be first in alphabetical
|
|
* order.
|
|
*/
|
|
animations[0].name = add_string("###none");
|
|
animations[0].num_animations = 1;
|
|
animations[0].faces = malloc(sizeof(Fontindex));
|
|
animations[0].faces[0] = 0;
|
|
animations[0].facings = 0;
|
|
|
|
snprintf(buf, sizeof(buf), "%s/animations", settings.datadir);
|
|
LOG(llevDebug, "Reading animations from %s...\n", buf);
|
|
if ((fp = fopen(buf, "r")) == NULL) {
|
|
LOG(llevError, "Cannot open animations file %s: %s\n", buf, strerror_local(errno, buf, sizeof(buf)));
|
|
exit(-1);
|
|
}
|
|
while (fgets(buf, MAX_BUF-1, fp) != NULL) {
|
|
if (*buf == '#')
|
|
continue;
|
|
if (strlen(buf) == 0)
|
|
break;
|
|
/* Kill the newline */
|
|
buf[strlen(buf)-1] = '\0';
|
|
if (!strncmp(buf, "anim ", 5)) {
|
|
if (num_frames) {
|
|
LOG(llevError, "Didn't get a mina before %s\n", buf);
|
|
num_frames = 0;
|
|
}
|
|
num_animations++;
|
|
if (num_animations == animations_allocated) {
|
|
animations = realloc(animations, sizeof(Animations)*(animations_allocated+10));
|
|
animations_allocated += 10;
|
|
}
|
|
animations[num_animations].name = add_string(buf+5);
|
|
animations[num_animations].num = num_animations; /* for bsearch */
|
|
animations[num_animations].facings = 1;
|
|
} else if (!strncmp(buf, "mina", 4)) {
|
|
animations[num_animations].faces = malloc(sizeof(Fontindex)*num_frames);
|
|
for (i = 0; i < num_frames; i++)
|
|
animations[num_animations].faces[i] = faces[i];
|
|
animations[num_animations].num_animations = num_frames;
|
|
if (num_frames%animations[num_animations].facings) {
|
|
LOG(llevDebug, "Animation %s frame numbers (%d) is not a multiple of facings (%d)\n",
|
|
animations[num_animations].name, num_frames, animations[num_animations].facings);
|
|
}
|
|
num_frames = 0;
|
|
} else if (!strncmp(buf, "facings", 7)) {
|
|
if (!(animations[num_animations].facings = atoi(buf+7))) {
|
|
LOG(llevDebug, "Animation %s has 0 facings, line=%s\n",
|
|
animations[num_animations].name, buf);
|
|
animations[num_animations].facings = 1;
|
|
}
|
|
|
|
} else {
|
|
if (!(faces[num_frames++] = find_face(buf, 0)))
|
|
LOG(llevDebug, "Could not find face %s for animation %s\n",
|
|
buf, animations[num_animations].name);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
LOG(llevDebug, "done. got (%d)\n", num_animations);
|
|
}
|
|
|
|
/**
|
|
* Utility function to compare 2 animations based on their name.
|
|
* Used for sorting/searching.
|
|
*/
|
|
static int anim_compare(const Animations *a, const Animations *b) {
|
|
return strcmp(a->name, b->name);
|
|
}
|
|
|
|
/**
|
|
* Finds the animation id that matches name. Will LOG() an error if not found.
|
|
* @param name
|
|
* the animation's name.
|
|
* @return
|
|
* animation number, or 0 if no match found (animation 0 is initialized as the 'bug' face).
|
|
* @see try_find_animation
|
|
*/
|
|
int find_animation(const char *name) {
|
|
int face = try_find_animation(name);
|
|
if (!face)
|
|
LOG(llevError, "Unable to find animation %s\n", name);
|
|
return face;
|
|
}
|
|
|
|
/**
|
|
* Tries to find the animation id that matches name, don't LOG() an error if not found.
|
|
* @param name
|
|
* the animation's name.
|
|
* @return
|
|
* animation number, or 0 if no match found (animation 0 is initialized as the 'bug' face).
|
|
* @see find_animation
|
|
*/
|
|
int try_find_animation(const char *name) {
|
|
Animations search, *match;
|
|
|
|
search.name = name;
|
|
|
|
match = (Animations *)bsearch(&search, animations, (num_animations+1), sizeof(Animations), (int (*)(const void *, const void *))anim_compare);
|
|
|
|
|
|
if (match)
|
|
return match->num;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Updates the face-variable of an object.
|
|
* If the object is the head of a multi-object, all objects are animated.
|
|
* The object's state is not changed, but merely updated if needed (out of bounds of
|
|
* new animation, reached end of animation, ...)
|
|
*
|
|
* @param op is the object to animate.
|
|
* @param dir is the direction the object is facing. This is generally same as
|
|
* op->direction, but in some cases, op->facing is used instead - the
|
|
* caller has a better idea which one it really wants to be using,
|
|
* so let it pass along the right one.
|
|
*/
|
|
void animate_object(object *op, int dir) {
|
|
int max_state; /* Max animation state object should be drawn in */
|
|
int base_state; /* starting index # to draw from */
|
|
int oldface = op->face->number;
|
|
|
|
if (!op->animation_id || !NUM_ANIMATIONS(op)) {
|
|
StringBuffer *sb;
|
|
char *diff;
|
|
|
|
LOG(llevError, "Object lacks animation.\n");
|
|
sb = stringbuffer_new();
|
|
dump_object(op, sb);
|
|
diff = stringbuffer_finish(sb);
|
|
LOG(llevError, diff);
|
|
free(diff);
|
|
return;
|
|
}
|
|
|
|
if (op->head) {
|
|
dir = op->head->direction;
|
|
|
|
if (NUM_ANIMATIONS(op) == NUM_ANIMATIONS(op->head))
|
|
op->state = op->head->state;
|
|
}
|
|
|
|
/* If object is turning, then max animation state is half through the
|
|
* animations. Otherwise, we can use all the animations.
|
|
*/
|
|
max_state = NUM_ANIMATIONS(op)/NUM_FACINGS(op);
|
|
base_state = 0;
|
|
/* at least in the older aniamtions that used is_turning, the first half
|
|
* of the animations were left facing, the second half right facing.
|
|
* Note in old the is_turning, it was set so that the animation for a monster
|
|
* was always towards the enemy - now it is whatever direction the monster
|
|
* is facing.
|
|
*/
|
|
if (NUM_FACINGS(op) == 2) {
|
|
if (dir < 5)
|
|
base_state = 0;
|
|
else
|
|
base_state = NUM_ANIMATIONS(op)/2;
|
|
} else if (NUM_FACINGS(op) == 4) {
|
|
if (dir == 0)
|
|
base_state = 0;
|
|
else
|
|
base_state = ((dir-1)/2)*(NUM_ANIMATIONS(op)/4);
|
|
} else if (NUM_FACINGS(op) == 8) {
|
|
if (dir == 0)
|
|
base_state = 0;
|
|
else
|
|
base_state = (dir-1)*(NUM_ANIMATIONS(op)/8);
|
|
}
|
|
|
|
/* If beyond drawable states, reset */
|
|
if (op->state >= max_state) {
|
|
op->state = 0;
|
|
if (op->temp_animation_id) {
|
|
op->temp_animation_id = 0;
|
|
/* op->last_anim = 0; */
|
|
/* update_object(op, UP_OBJ_FACE); */
|
|
animate_object(op, dir);
|
|
return;
|
|
}
|
|
}
|
|
SET_ANIMATION(op, op->state+base_state);
|
|
|
|
if (op->face == blank_face)
|
|
op->invisible = 1;
|
|
|
|
/* This block covers monsters (eg, pixies) which are supposed to
|
|
* cycle from visible to invisible and back to being visible.
|
|
* as such, disable it for players, as then players would become
|
|
* visible.
|
|
*/
|
|
else if (op->type != PLAYER && QUERY_FLAG((&op->arch->clone), FLAG_ALIVE)) {
|
|
if (op->face->number == 0) {
|
|
op->invisible = 1;
|
|
CLEAR_FLAG(op, FLAG_ALIVE);
|
|
} else {
|
|
op->invisible = 0;
|
|
SET_FLAG(op, FLAG_ALIVE);
|
|
}
|
|
}
|
|
|
|
if (op->more)
|
|
animate_object(op->more, dir);
|
|
|
|
/* update_object will also recursively update all the pieces.
|
|
* as such, we call it last, and only call it for the head
|
|
* piece, and not for the other tail pieces.
|
|
*/
|
|
if (!op->head && (oldface != op->face->number))
|
|
update_object(op, UP_OBJ_FACE);
|
|
}
|
|
|
|
/**
|
|
* Applies a compound animation to an object.
|
|
*
|
|
* @param who
|
|
* object to apply the animation to. Must not be NULL.
|
|
* @param suffix
|
|
* animation suffix to apply. Must not be NULL.
|
|
*/
|
|
void apply_anim_suffix(object *who, sstring suffix) {
|
|
int anim;
|
|
object *head, *orig;
|
|
char buf[MAX_BUF];
|
|
|
|
assert(who);
|
|
assert(suffix);
|
|
|
|
if (who->temp_animation_id != 0)
|
|
/* don't overlap animation, let the current one finish. */
|
|
return;
|
|
|
|
if (who->head != NULL)
|
|
head = who->head;
|
|
else
|
|
head = who;
|
|
orig = head;
|
|
snprintf(buf, MAX_BUF, "%s_%s", animations[head->animation_id].name, suffix);
|
|
anim = try_find_animation(buf);
|
|
if (anim) {
|
|
for (; head != NULL; head = head->more) {
|
|
head->temp_animation_id = anim;
|
|
head->temp_anim_speed = animations[anim].num_animations/animations[anim].facings;
|
|
head->temp_last_anim = 0;
|
|
head->last_anim = 0;
|
|
head->state = 0;
|
|
}
|
|
animate_object(orig, orig->facing);
|
|
}
|
|
}
|