Hello all,
It seems I've hit a wall with my poor coding skills. :(
I have a code that creates custom animations based on what the player is equppied with (both clothes and weapons). The original worked like this: each item had custom properties linked with views for each of the default action, the script took sprites from those views and combined into the final look. This worked fine, however, it was tedious and time consuming work setting up all the initial "item views". So I've reworked it - now, the item property points to a sprite with a spritesheet, and the script takes those spritesheets, combines them and transforms it into the final view.
For some reason I can't understand, sometimes it does not work properly. The only part that causes problems is the attack view part - it might be because as opposed to walk and death views, the attack views's differ in number of frames per view and the frame size.
What happens is that especially in the "unarmed" view, some frames are mixed in from different views - ingame it might look as if instead of taking a battle stance with raised fists, a random part of shooting a gun is shown (even from different loop, and not from the first frame).
As you can see from the script, I've tried debugging it by saving the sprites from the sprite array, to actually see what's going on inside. In the folder "sprite_array", it seems there are some leftover sprites if they are not used. For example view[8] used to show attack with a handgun, now it stores attack with a knife - the handgun animation had more sprites, so there are some unused sprites with a handgun at the end. Seems it works as expected.
Now, the "view_array" folder stores all the actual sprites used in the displayed views - and everything looks exactly right. But ingame one of the animation is broken - even though the log panel shows the correct view and loop number is used for the animation, the graphic shown in game doesn't match what's stored in the folder. And honestly, I have no idea how that would even be possible. I must have missed some small detail somewhere!
Sorry for the length and probably overall poor quality of the code - this is what happens when someone with no coding education keeps editing and reworking something for the past 10 years. (roll)
I totally understand if it's just too much to go through, but maybe someone will be able to spot the problem right away! Or maybe there's some problem right in the core of the idea?
Thank you all! :)
struct ViewGenerating {
DynamicSprite *walk_frame[75]; // maximum should be 8 loops with 9 frames = 72 frames
};
ViewGenerating CustomView[10];
int i_view;
ViewFrame *frame_new;
ViewFrame *frame_old;
DynamicSprite *gv_sprite_base;
DrawingSurface *gv_sprite_base_surface;
function GenerateView(this Character*, ViewType type, int view_index)
{
// view_index = 0, 1, 2 (based on the weapon)
i_loop = 0;
gv_sheet_loop = 0;
i_loop_frame = 0; // the frame number of the current loop
/*
view_clothes[] index:
shadow 7
X NOT USED back arm 7
shoes 6
trousers 5
vest 4
X NOT USED front arm 3
weapon 3
hat 2
(slot 1 and 0 free for now)
*/
if (type == eViewWalk) {
view_clothes[7] = 20; // shadow sprite
if (weapon_slot[7] != null) view_clothes[2] = weapon_slot[7].GetProperty("IGWalk");
else view_clothes[2] = 0; // 7 is hat, 0 means skip; index 2 sends it to front
if (weapon_slot[6] != null) view_clothes[6] = weapon_slot[6].GetProperty("IGWalk");
else view_clothes[6] = 999; // change to sprite number
if (weapon_slot[5] != null) view_clothes[5] = weapon_slot[5].GetProperty("IGWalk");
else view_clothes[5] = 999; // change to sprite number
if (weapon_slot[4] != null) view_clothes[4] = weapon_slot[4].GetProperty("IGWalk");
else view_clothes[4] = 999; // change to sprite number
if (weapon_slot[view_index] != null)
{
if (weapon_slot[view_index].GetProperty("weapon_type") == 0)
{
if (weapon_slot[view_index].GetTextProperty("special_bonus") == "knife") view_clothes[3] = 13; //view_clothes = sprite number
else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "axe") view_clothes[3] = 13; // UPDATE WHEN READY
else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "saber") view_clothes[3] = 13; // UPDATE WHEN READY
else view_clothes[3] = 0; // fist, no weapon
}
else if (weapon_slot[view_index].GetProperty("weapon_type") == 1) view_clothes[3] = 15;
else if (weapon_slot[view_index].GetProperty("weapon_type") == 2) view_clothes[3] = 17;
else view_clothes[3] = 0; // medkit
}
else view_clothes[3] = 0; // null weapon
i_view = view_index; // i_view - sets the index for storing sprites (0-8); 0, 1, 2 = walk, 3, 4, 5 = death, 6, 7, 8 = attack
if (view_index == 0) view_to_generate = PLAYER_WALK0;
else if (view_index == 1) view_to_generate = PLAYER_WALK1;
else if (view_index == 2) view_to_generate = PLAYER_WALK2;
else
{
Display("Wrong parameter of GenerateView function.");
return -1;
}
}
else if (type == eViewDeath) {
view_clothes[7] = 37; //shadow
if (weapon_slot[7] != null) view_clothes[2] = weapon_slot[7].GetProperty("IGDeath");
else view_clothes[2] = 0; // 7 is hat, 0 means skip
if (weapon_slot[6] != null) view_clothes[6] = weapon_slot[6].GetProperty("IGDeath");
else view_clothes[6] = 999; // CHANGE TO SPRITE NUMBER
if (weapon_slot[5] != null) view_clothes[5] = weapon_slot[5].GetProperty("IGDeath");
else view_clothes[5] = 999; // CHANGE TO SPRITE NUMBER
if (weapon_slot[4] != null) view_clothes[4] = weapon_slot[4].GetProperty("IGDeath");
else view_clothes[4] = 999; // CHANGE TO SPRITE NUMBER
if (weapon_slot[view_index] != null)
{
if (weapon_slot[view_index].GetProperty("weapon_type") == 0) {
if (weapon_slot[view_index].GetTextProperty("special_bonus") == "knife") view_clothes[3] = 42; // sprite knife death
else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "axe") view_clothes[3] = 0;
else if (weapon_slot[view_index].GetTextProperty("special_bonus") == "saber") view_clothes[3] = 0;
else view_clothes[3] = 0;
}
else if (weapon_slot[view_index].GetProperty("weapon_type") == 1) view_clothes[3] = 41; // sprite handgun death
else if (weapon_slot[view_index].GetProperty("weapon_type") == 2) view_clothes[3] = 43;
else view_clothes[3] = 0;
}
else view_clothes[3] = 0;
i_view = view_index+3; // is i_view neccessary?
if (view_index == 0) view_to_generate = PLAYER_DEATH0;
else if (view_index == 1) view_to_generate = PLAYER_DEATH1;
else if (view_index == 2) view_to_generate = PLAYER_DEATH2;
else
{
Display("Wrong parameter of GenerateView function.");
return -1;
}
}
else if (type == eViewAttack)
{
if (weapon_slot[view_index] == null) // nothing => fighting with fists view
{
view_to_generate = PLAYER_A_NULL;
gv_property = "IGAFist";
}
else if (weapon_slot[view_index].GetProperty("weapon_type") == 0) // close combat
{
if (weapon_slot[view_index] == iFist)
{
view_to_generate = PLAYER_A_NULL;
gv_property = "IGAFist";
}
else
{
gv_property = "IGAKnife";
view_to_generate = PLAYER_A_KNIFE;
}
}
else if (weapon_slot[view_index].GetProperty("weapon_type") == 1) // handguns
{
view_to_generate = PLAYER_A_GUN;
gv_property = "IGAGun";
}
else if (weapon_slot[view_index].GetProperty("weapon_type") == 2) // rifles
{
view_to_generate = PLAYER_A_RIFLE;
gv_property = "IGARifle";
}
else // medkits or fallthrough
{
view_to_generate = PLAYER_A_NULL;
gv_property = "IGAFist";
}
i_view = 6+view_index; // i_view for attacks: 6, 7, 8
player_attack_view[view_index] = view_to_generate;
gv_i = 7;
while (gv_i > 3) // other layers handled separately
{
if (weapon_slot[gv_i] != null)
{
if (gv_i == 7) // hat
{
view_clothes[2] = weapon_slot[7].GetProperty(gv_property);
view_clothes_transition[2] = weapon_slot[7].GetProperty(gv_property.Append("Transition"));
}
else
{
view_clothes[gv_i] = weapon_slot[gv_i].GetProperty(gv_property);
view_clothes_transition[gv_i] = weapon_slot[gv_i].GetProperty(gv_property.Append("Transition"));
}
}
else
{ // empty sprites
if (gv_i == 7) // HAT - specific, no hat means no sprite
{
view_clothes[2] = 0;
view_clothes_transition[2] = 0;
}
if (view_to_generate == PLAYER_A_NULL)
{
if (gv_i == 6) // SHOES
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 5) // TROUSERS
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 4) // VEST
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
}
else if (view_to_generate == PLAYER_A_KNIFE || view_to_generate == PLAYER_A_SABER || view_to_generate == PLAYER_A_AXE)
{
if (gv_i == 6) // SHOES
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 5) // TROUSERS
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 4) // VEST
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
}
else if (view_to_generate == PLAYER_A_GUN)
{
if (gv_i == 6) // SHOES
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 5) // TROUSERS
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 4) // VEST
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
}
else if (view_to_generate == PLAYER_A_RIFLE)
{
if (gv_i == 6) // SHOES
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 5) // TROUSERS
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
else if (gv_i == 4) // VEST
{
view_clothes[gv_i] = 999;
view_clothes_transition[gv_i] = 999;
}
}
}
gv_i --;
}
// VIEW SPECIFIC SPRITESHEETS
if (view_to_generate == PLAYER_A_NULL)
{
view_clothes[7] = 84; // shadow sprite
view_clothes_transition[7] = 372;
view_clothes[3] = 0; // weapon sprite
view_clothes_transition[3] = 0;
}
else if (view_to_generate == PLAYER_A_KNIFE || view_to_generate == PLAYER_A_SABER || view_to_generate == PLAYER_A_AXE)
{
view_clothes[7] = 21; // shadow sprite
view_clothes_transition[7] = 32;
if (view_to_generate == PLAYER_A_SABER)
{
view_clothes[3] = 0; // weapon sprite
view_clothes_transition[3] = 0;
}
else if (view_to_generate == PLAYER_A_AXE)
{
view_clothes[3] = 0; // weapon sprite
view_clothes_transition[3] = 0;
}
else // knife
{
view_clothes[3] = 98; // weapon sprite
view_clothes_transition[3] = 99;
}
}
else if (view_to_generate == PLAYER_A_GUN)
{
view_clothes[7] = 55; // shadow sprite
view_clothes_transition[7] = 44;
view_clothes[3] = 0; // weapon sprite
view_clothes_transition[3] = 0;
}
else if (view_to_generate == PLAYER_A_RIFLE)
{
view_clothes[7] = 100; // shadow sprite
view_clothes_transition[7] = 101;
view_clothes[3] = 0; // weapon sprite
view_clothes_transition[3] = 0;
}
}
// setup the base size of the sprite
loopcount = Game.GetLoopCountForView(view_to_generate);
framecount = Game.GetFrameCountForLoop(view_to_generate, 0);
i_frame = 0; // loops through the complete array for dynamic sprites
i_loop = -1; // first tick
// draw the sprites
while (i_loop < loopcount)
{
if (i_loop_frame == framecount || i_loop == -1)
{
i_loop_frame = 0;
i_loop ++;
if (i_loop >= loopcount) break;
// setup which line of spritesheet to use
if ((i_loop - 8*(i_loop/8)) == 2) gv_sheet_loop = 1;
else if ((i_loop - 8*(i_loop/8)) == 3) gv_sheet_loop = 2;
else if ((i_loop - 8*(i_loop/8)) == 4 || (i_loop - 8*(i_loop/8)) == 6) gv_sheet_loop = 3;
else if ((i_loop - 8*(i_loop/8)) == 5 || (i_loop - 8*(i_loop/8)) == 7) gv_sheet_loop = 4;
else gv_sheet_loop = i_loop - 8*(i_loop/8); // makes sure it works for transition too
if (i_loop < Game.GetLoopCountForView(view_to_generate))
{
framecount = Game.GetFrameCountForLoop(view_to_generate, i_loop);
if (i_loop == 0 || i_loop == 8)
{
// start and transition - change the base size of the sprite
if (i_loop == 0)
{
gv_frame_width = PlayerAnimSize[view_to_generate].Width;
gv_frame_height = PlayerAnimSize[view_to_generate].Height;
}
else if (i_loop == 8)
{
gv_frame_width = PlayerAnimSize[view_to_generate].TransitionWidth;
gv_frame_height = PlayerAnimSize[view_to_generate].TransitionHeight;
}
if (CustomView[i_view].walk_frame[i_frame] == null) CustomView[i_view].walk_frame[i_frame] = DynamicSprite.Create(gv_frame_width, gv_frame_height, true); // new sprite created
else CustomView[i_view].walk_frame[i_frame].ChangeCanvasSize(gv_frame_width, gv_frame_height, 0, 0);
viewsurface = CustomView[i_view].walk_frame[i_frame].GetDrawingSurface(); // surface created from dynamic sprite from the view
viewsurface.Clear();
if (i_loop == 0 && i_loop_frame == 1)
{
// space for debug message
}
i_count = 7; // index of layers, from back to front
while (i_count > 1) // 0 and 1 is unused for now, can be changed - hairstyles?
{ // cycle through
if (i_loop < 8)
{ // standard loops
if (view_clothes[i_count] != 0) viewsurface.DrawImage(0, 0, view_clothes[i_count], 0, gv_frame_width, gv_frame_height, gv_frame_width*i_loop_frame, gv_frame_height*gv_sheet_loop, gv_frame_width, gv_frame_height);
}
else
{ // transition - change the spritesheet
if (view_clothes_transition[i_count] != 0) viewsurface.DrawImage(0, 0, view_clothes_transition[i_count], 0, gv_frame_width, gv_frame_height, gv_frame_width*i_loop_frame, gv_frame_height*gv_sheet_loop, gv_frame_width, gv_frame_height);
}
i_count --;
}
viewsurface.Release(); // done with one frame
frame_new = Game.GetViewFrame(view_to_generate, i_loop, i_loop_frame);
frame_new.Graphic = CustomView[i_view].walk_frame[i_frame].Graphic;
if (type == eViewAttack) // only part that causes problems
{
file_name_debug = String.Format("$SAVEGAMEDIR$/view-%d_frame-%d_loop-%d_frame-%d.bmp", i_view, i_frame, i_loop, i_loop_frame);
CustomView[i_view].walk_frame[i_frame].SaveToFile(file_name_debug);
file_name_debug = String.Format("$SAVEGAMEDIR$/sprites_array/view-%d_frame-%d.bmp", i_view, i_frame);
CustomView[i_view].walk_frame[i_frame].SaveToFile(file_name_debug);
file_name_debug = String.Format("$SAVEGAMEDIR$/view_array/%d/loop-%d_frame-%d.bmp", view_to_generate, i_loop, i_loop_frame);
DynamicSprite *debug_sprite = DynamicSprite.CreateFromExistingSprite(frame_new.Graphic);
debug_sprite.SaveToFile(file_name_debug);
}
i_loop_frame ++;
i_frame ++;
}
}
One thing I've noticed:
i_loop - 8*(i_loop/8) == 2
can be shortened to
i_loop % 8 == 2
Anyway, it sounds like the game is using the wrong view, a view with too many frames. So you're using the correct loop and frame but a view that has more frames, and in your animation you first see the correct first X frames, then also frames X+1, X+2 which still have sprites in them from a different view.
Your code uses view_to_generate to grab the number of frames but then uses i_view to actually populate it. If these values differ, and they appear to, then the loop might end up putting the wrong number of frames in the view, i.e. less.
Edit:
Scratch that, I just saw that you are saving the generated frames using view_generate.
If I had to debug this I'd probably refactor the code into a bunch of separate functions. Using one massive function and tons of variables makes this a pain to debug.
Quote from: Khris on Sun 25/05/2025 11:10:59One thing I've noticed:
i_loop - 8*(i_loop/8) == 2
can be shortened to
i_loop % 8 == 2
Oh, thanks, that's great! I'll start using it from now on. :)
Quote from: Khris on Sun 25/05/2025 11:10:59If I had to debug this I'd probably refactor the code into a bunch of separate functions. Using one massive function and tons of variables makes this a pain to debug.
I guess that's the way to go. First I'll try to go through the whole function once more, and if that doesn't help, I'll have to split it into separate parts and debug them individually. Thank you for taking your time with it! :)
[EDIT]
I guess what might be happening is that the array of dynamic sprites
walk_frame inside
CustomView is interconnected? Meaning that the value for
CustomView[number1].walk_frame[number2].Graphic used by the view might change without accessing it, simply by changing the total number of sprites stored in the variable - is it possible?