1# DataHelper contains subroutines useful for loading a character's
2# frames, and creating his states.
3
4use strict;
5
6
7require 'FighterStats.pl';
8
9
10
11=comment
12
13
14SITUATIONS ARE:
15
16Ready, Stand, Crouch, (Midair = any + character is flying), Falling
17
18
19
20SITUATION DEPENDENT EVENTS ARE:
21
22Highhit, Uppercut, Hit, Groinhit, Leghit, Fall
23
24
25
26STANDBY EVENTS ARE:
27
28Won, Hurt, Threat, Fun, Turn
29
30
31
32
33________|___Ready___________Block___Stand___________Crouch______________Midair______Falling
34Highhit	|	HighPunched		-		HighPunched		KneelingPunched 	(...)		(...)
35Uppercut|	Falling			-		Falling			KneelingPunched		(...)		(...)
36Hit		|	LowPunched		-		LowPunched		KneelingKicked
37Groinhit|	GroinKicked		-		GroinKicked		KneelingKicked
38Leghit	|	Swept			-		Swept			KneelingKicked
39Fall	|	Falling			-		Falling			KneelingPunched
40
41
42
43FRAME MEMBER DESCRIPTION IS:
44
45x		int		X coordinate offset of the image relative to the character's anchor.
46y		int		Y coordinate offset of the image relative to the character's anchor.
47w		int		The width of the image.
48h		int		The height of the image.
49head	array	The coordinates of a polygon marking the head within the image, relative to the anchor.
50body	array	The coordinates of a polygon marking the body within the image, relative to the anchor.
51legs	array	The coordinates of a polygon marking the legs within the image, relative to the anchor.
52hit		array	The coordinates of a polygon marking the hit within the image, relative to the anchor.
53
54
55STATE MEMBER DESCRIPTION IS:
56
57F		int		The number of the visible frame.
58SITU	string	The situation associated with this state (Ready, Stand, Crouch, Falling)
59DEL		int		The delay before moving to the next state.
60NEXTST	string	The name of the state which follows this state, if none of the CONs is used.
61CON		hash	Connections from this state. The keys are either events or keyboard input.
62HIT		?		The hit delivered at the beginning of this state.
63BLOCK	int		If true, the character is blocking in his current state.
64MOVE	int		The character's anchor should continously move this much during this state.
65DELTAX	int		The character's anchor should immediately change by this much after this state.
66PUSHX	int		The character is pushed, with this much momentum upon entering this state.
67TURN	int		If true, the character's facing direction changes after this state.
68JUMP	int		The character leaps into the air, with this much initial momentum upon entering this state.
69DOODAD	string	A doodad is created at the beginning of this state. The string contains the doodad's type and speed.
70SOUND	string	The sound effect associated with this state (if any);
71HITSND	string	The sound effect if the HIT is successful.
72MISSSND	string	The sound effect if the HIT fails.
73CODE	string	This code will be evaled at the beginning of this state.
74LAYER	int		The "priority" of the graphics. 5: hurt; 10: block; 15: kneeling; 20: normal; 25: attack
75
76
77=cut
78
79
80
81
82# Loads the frame data (x, y, w, h) from the given datafile.
83# The path to the datafile is inserted automatically. The frame data will
84# be shifted by (-PivotX,-PivotY).
85#
86# Returns an array of frame data. The first element in the array is
87# a dummy entry, the second is the first real entry. This is because
88# the first thing in the datafile is a PAL entry.
89#
90# Example: LoadFrames( "ZOLIDATA.DAT.txt" );
91sub LoadFrames ($$$)
92{
93	my ($DataName, $PivotX, $PivotY) = @_;
94	my (@Frames, $data, $frame, $DatName);
95
96	# Make sure that Whatever.dat also exists.
97	$DatName = $DataName;
98	$DatName =~ s/\.txt$//;
99	open DATFILE, "../characters/$DatName" || die ("Couldn't open ../characters/$DatName");
100	close DATFILE;
101
102	open DATAFILE, "../characters/$DataName" || die ("Couldn't open ../characters/$DataName");
103	$data = '';
104	while ( read DATAFILE, $data, 16384, length($data) )
105	{
106	}
107	close DATAFILE;
108
109	print "$DataName file is ", length($data), " bytes long.\n";
110
111	# Insert a dummy first row for the palette entry
112	eval ("\@Frames = ( {}, $data);");
113	die $@ if $@;
114
115	foreach $frame (@Frames)
116	{
117		OffsetFrame( $frame, -$PivotX, -$PivotY );
118	}
119	print "$DataName loaded, ", scalar @Frames, " frames.\n";
120
121	return @Frames;
122}
123
124
125# Creates a frame lookup from a descriptor array.
126# The first parameter is the expected number of frames.
127# The descriptor array should have frame base names in even positions,
128# and lengths at odd. positions. For example:
129# ("start", 6, "stand", 4, ... )
130#
131# The routine will return a lookup which will contain the frame's logical
132# name as a key, and its physical index as a value. The logical names are
133# simply the basename plus a number. The example above would return:
134# ("start1"=>1, "start2"=>2, ..., "start6"=>6, "stand1"=>6, "stand2"=>7, ...)
135sub CreateFrameLookup
136{
137	my ($ExpectedCount, @FrameDesc) = @_;
138
139	my ($FrameName, $NumFrames);
140	my ($i, $j);
141	my (%FrameLookup);
142
143	for ( $i=0; $i<scalar @FrameDesc; $i +=2 )
144	{
145		$FrameName = $FrameDesc[$i];
146		$NumFrames = $FrameDesc[$i+1];
147
148		for ( $j = 1; $j<=$NumFrames; ++$j )
149		{
150			# print "Frame ", (scalar keys %FrameLookup) + 1, " is now called $FrameName$j\n";
151			print "Name redefinition: $FrameName!\n" if defined $FrameLookup{ "$FrameName$j" };
152			$FrameLookup{ "$FrameName$j" } = (scalar keys %FrameLookup) + 1;
153		}
154	}
155
156	if ( $ExpectedCount !=  scalar keys( %FrameLookup ) )
157	{
158		die( "Expected number of frames ($ExpectedCount) doesn't equal the actual number of frames: ".
159			scalar keys(%FrameLookup) );
160	}
161
162	return %FrameLookup;
163}
164
165
166
167# Helper function. Finds the last frame with a given name in a frame
168# lookup structure. Return the index of the last frame (1-based), or
169# 0 if none with the given name were found.
170#
171# Example: If there are 6 "highpunch" frames (highpunch1 to highpunch6),
172# FindLastFrame(\%FrameLookup, "highpunch") returns 6.
173sub FindLastFrame($$) {
174	my ($FrameLookup, $FrameName) = @_;
175	my ($i) = (1);
176	while ( exists ${$FrameLookup}{"$FrameName$i"} ) { $i++; }
177	return $i-1;
178}
179
180
181
182sub OffsetPolygon($$$)
183{
184	my ($poly, $dx, $dy) = @_;
185	my ($i, $n);
186
187	$n = scalar @{$poly};
188
189	for ( $i=0; $i < $n; $i+=2 )
190	{
191		$poly->[$i] += $dx;
192		$poly->[$i+1] += $dy;
193	}
194}
195
196
197
198sub MirrorPolygon($)
199{
200	my ($poly) = @_;
201	my ($i, $n);
202
203	$n = scalar @{$poly};
204
205	for ( $i=0; $i < $n; $i+=2 )
206	{
207		$poly->[$i] = - $poly->[$i];
208	}
209}
210
211
212sub GetPolygonCenter($)
213{
214	my ($poly) = @_;
215
216	my ($i, $n, $x, $y);
217
218	$n = scalar @{$poly};
219	$x = $y = 0;
220	for ( $i=0; $i < $n; $i+=2 )
221	{
222		$x += $poly->[$i];
223		$y += $poly->[$i+1];
224	}
225
226	return ( $x*2/$n, $y*2/$n );
227}
228
229
230
231sub OffsetFrame($$$) {
232	my ($frame, $dx, $dy) = @_;
233
234	$frame->{'x'} += $dx;
235	$frame->{'y'} += $dy;
236
237	OffsetPolygon( $frame->{head}, $dx, $dy ) if defined ($frame->{head});
238	OffsetPolygon( $frame->{body}, $dx, $dy ) if defined ($frame->{body});
239	OffsetPolygon( $frame->{legs}, $dx, $dy ) if defined ($frame->{legs});
240	OffsetPolygon( $frame->{hit}, $dx, $dy )  if defined ($frame->{hit});
241}
242
243
244
245# FindLastState returns the last index of a given state.
246# For example, if Punch4 is the last in Punch, FindLastState("Punch") is 4.
247sub FindLastState($$) {
248	my ( $States, $StateName ) = @_;
249	my ( $i ) = ( 1 );
250
251	while ( exists ${$States}{ "$StateName $i" } ) { $i++; }
252	return $i-1;
253}
254
255
256# Translates an abbreviated sequence to a full sequence.
257# "-punch" is every punch frame backwards.
258# "_punch" is every punch frame except the last one backwards.
259# "+punch" is every punch frame forwards.
260sub TranslateSequence($$) {
261	my ($FrameLookup, $Sequence) = @_;
262
263	my ($pre, $frame) = $Sequence =~ /^([+-_]{0,1})(\w+)/;
264	my ($LastFrame) = (FindLastFrame( $FrameLookup, $frame ) );
265	#$LastFrame = (FindLastFrame( $FrameLookup, "$pre$frame" ) ) if $LastFrame == 0;
266	#print "Last frame of $frame is $LastFrame.\n";
267
268	return "$frame 1-$LastFrame" if ( $pre eq '+' );
269	return "$frame $LastFrame-1" if ( $pre eq '-' );
270	return "$frame " . ($LastFrame-1) . "-1" if ( $pre eq '_' );
271
272	$Sequence =~ s/\sn(-{0,1})/ $LastFrame$1/;	# Replace n- with last frame
273	$Sequence =~ s/-n/-$LastFrame/;				# Replace -n with last frame
274	return $Sequence;
275}
276
277
278
279sub SetStateData($$$)
280{
281	my ($state, $FrameDesc, $suffix) = @_;
282
283	$state->{DEL} = $FrameDesc->{"DEL$suffix"} if defined $FrameDesc->{"DEL$suffix"};
284	$state->{HIT} = $FrameDesc->{"HIT$suffix"} if defined $FrameDesc->{"HIT$suffix"};
285	$state->{CON} = $FrameDesc->{"CON$suffix"} if defined $FrameDesc->{"CON$suffix"};
286	$state->{BLOCK} = $FrameDesc->{"BLOCK$suffix"} if defined $FrameDesc->{"BLOCK$suffix"};
287	$state->{NEXTST} = $FrameDesc->{"NEXTST$suffix"} if defined $FrameDesc->{"NEXTST$suffix"};
288	$state->{MOVE} = $FrameDesc->{"MOVE$suffix"} if defined $FrameDesc->{"MOVE$suffix"};
289	$state->{DELTAX} = $FrameDesc->{"DELTAX$suffix"} if defined $FrameDesc->{"DELTAX$suffix"};
290	$state->{PUSHX} = $FrameDesc->{"PUSHX$suffix"} if defined $FrameDesc->{"PUSHX$suffix"};
291	$state->{TURN} = $FrameDesc->{"TURN$suffix"} if defined $FrameDesc->{"TURN$suffix"};
292	$state->{JUMP} = $FrameDesc->{"JUMP$suffix"} if defined $FrameDesc->{"JUMP$suffix"};
293	$state->{SITU} = $FrameDesc->{"SITU$suffix"} if defined $FrameDesc->{"SITU$suffix"};
294	$state->{DOODAD} = $FrameDesc->{"DOODAD$suffix"} if defined $FrameDesc->{"DOODAD$suffix"};
295	$state->{SOUND} = $FrameDesc->{"SOUND$suffix"} if defined $FrameDesc->{"SOUND$suffix"};
296	$state->{CODE} = $FrameDesc->{"CODE$suffix"} if defined $FrameDesc->{"CODE$suffix"};
297}
298
299
300
301# Adds a sequence to the end of a state
302# Sequences are: e.g. "throw 10-14, throw 16, throw 14-10"
303# Each piece of the sequence will have $Delay delay.
304sub AddStates($$$) {
305	my ( $States, $Frames, $FrameDesc ) = @_;
306	my ( $StateName, $SequenceString, $LastState, $i, $sloop, $s, @Sequences );
307	my ( $from, $to, $frame, $state );
308
309	$StateName = $FrameDesc->{'N'};
310	$SequenceString = $FrameDesc->{'S'};
311	$FrameDesc->{SITU} = 'Stand' unless defined $FrameDesc->{SITU};
312	$LastState = FindLastState($States,$StateName)+1;
313
314	@Sequences  = split ( /\s*,\s*/, $SequenceString );
315	for ( $sloop = 0; $sloop < scalar @Sequences; ++$sloop )
316	{
317		$s = TranslateSequence( $Frames, $Sequences[$sloop] );
318		#print "Sequence is $s\n";
319
320		if ( $s =~ /^\s*(\w+)\s+(\d+)-(\d+)\s*$/ )
321		{
322			# Sequence is '<frame> <from>-<to>'
323			$frame = $1;
324			$from = $2;
325			$to = $3;
326		}
327		elsif ( $s =~ /^\s*(\w+)\s+(\d+)\s*$/ )
328		{
329			# Sequence is '<frame> <number>'
330			$frame = $1;
331			$from = $to = $2;
332		}
333		else
334		{
335			die "Sequence '$s' incorrect.\n";
336		}
337
338		$i = $from;
339		while (1)
340		{
341			die "Error: Frame $frame$i doesn't exist.\n"
342				unless defined ${$Frames}{"$frame$i"};
343
344			$state = { 'F'=>"$frame$i" };
345			SetStateData( $state, $FrameDesc, '' );
346			SetStateData( $state, $FrameDesc, $LastState );
347			if ( ( $sloop == scalar @Sequences -1 ) and ( $i == $to ) )
348			{
349				SetStateData( $state, $FrameDesc, 'N' );
350			}
351
352			$States->{"$StateName $LastState"} = $state;
353			# print "Added state '$StateName $LastState' as frame '$frame$i', delay $Delay\n";
354
355			$LastState++;
356			if ( $from < $to )
357			{
358				$i++;
359				last if $i > $to;
360			}
361			else
362			{
363				$i--;
364				last if $i < $to;
365			}
366		}
367
368	}
369}
370
371
372sub BlockStates($$)
373{
374	my ( $frames, $del) = @_;
375	my ( $retval, $i );
376
377	# We need to make sure that blocking is the same speed for every character.
378	# Typical is 5 frames, +- 1 frame
379	$del = int( 25 / $frames );			# 1/1
380
381	$retval = { 'N'=>'Block', 'DEL'=>$del, 'S'=>'+block', };
382	for ($i = 1; $i <= $frames; ++$i )
383	{
384		$retval->{"NEXTST$i"} = "Block " . ($i-1);
385		$retval->{"CON$i"} = { 'block'=> "Block " . ($i+1) };
386		$retval->{"BLOCK$i"} = 1 if $i*$del > 10;
387	}
388
389	$retval->{'NEXTST1'} = 'Stand';
390	$retval->{"CON$frames"} = { 'block'=> "Block " . $frames };
391
392	return $retval;
393}
394
395
396sub KneelingStates($$$$)
397{
398	my ( $frames, $frames2, $del, $con ) = @_;
399	my ( $retval, $retval2, $i, $j );
400
401	$retval = { 'N'=>'Kneeling', 'DEL'=> $del, 'S' => '+kneeling', 'SITU'=>'Crouch' };
402	for ( $i = 1; $i <= $frames; ++$i )
403	{
404		$retval->{"NEXTST$i"} = "Kneeling " . ($i-1);
405		$retval->{"CON$i"} = { 'down' => "Kneeling " . ($i+1) };
406	}
407	$retval->{'NEXTST1'} = 'Stand';
408	$retval->{"CON$frames"} = { 'down' => "Onknees" };
409
410	$retval2 = { 'N'=>'Onknees', 'DEL'=>$del, 'S' => '+onknees,-onknees', 'SITU'=>'Crouch',
411		'NEXTST' => "Kneeling $frames" };
412	$frames2 *= 2;
413	for ( $i = 1; $i <= $frames2; ++$i )
414	{
415		$j = ($i % $frames2) + 1;
416		$retval2->{"CON$i"} = { %{$con}, 'down'=>"Onknees $j", 'forw'=>"Onknees $j", 'back'=>"Onknees $j" };
417	}
418
419	return ($retval, $retval2);
420}
421
422
423
424=comment
425JumpStates is for generating the Jump, JumpFW, JumpBW, JumpFly,
426JumpStart, JumpKick, JumpPunch states for a state description list.
427
428Parameters:
429$frames		hash	The frame lookup hash.
430$con		hash	Connections during jumping (usually, JumpKick and JumpPunch only)
431$framenames	hash	[optional] If the standard frame names (kneeling,
432					onknees, kneelingkick, kneelingpunch) are not good, this has should
433					contain replacement names (e.g. 'kneelingkick' => 'sweep')
434=cut
435
436sub JumpStates
437{
438	my ( $frames, $con, $framenames ) = @_;
439	my ( $kneelingframes, $onkneesframes,
440		$kickframes, $punchframes ) = (
441			FindLastFrame( $frames, 'kneeling' ),
442			FindLastFrame( $frames, 'onknees' ),
443			FindLastFrame( $frames, 'kneelingkick' ),
444			FindLastFrame( $frames, 'kneelingpunch' ) );
445	my ( $jumpheight ) = 120;
446
447	my ( $i, $j, $statestotal, $statesdown, $statesknees, $deldown,
448		$jump, $jumpfw, $jumpbw, $flying, $flyingsequence, $flyingstart, $jumpkick, $jumppunch );
449
450	# The jump's first part is going down on knees, second part is
451	# on knees, third part is getting up.
452
453	if ( $::DELMULTIPLIER )
454	{
455		$statestotal = $jumpheight * 2 / 3 / $::DELMULTIPLIER; 				# 1/1
456	}
457	else
458	{
459		$statestotal = $jumpheight * 2 / 3;
460	}
461	$statesdown  = $statestotal / 4;
462
463	$deldown = int($statesdown / $kneelingframes + 0.1);			# 1/1
464	$statesdown  = $deldown * $kneelingframes;
465	$statesknees = $statestotal - $statesdown * 2;
466
467	$jump = { 'N'=>'Jump', 'DEL'=> $deldown, 'S'=>'kneeling 1-2, kneeling 1',
468		'JUMPN'=>$jumpheight, NEXTSTN=>'JumpFly', 'SOUND1'=>'PLAYER_JUMPS', };
469	$jumpfw = { %{$jump}, 'N'=>'JumpFW', 'PUSHX3'=>18*16 };
470	$jumpbw = { %{$jump}, 'N'=>'JumpBW', 'PUSHX3'=>-9*16 };
471
472	$flyingsequence = '';
473	$flying = {};
474	for ( $i = 0; $i < $statesknees / $deldown; ++$i )				#1/1
475	{
476		$j = $i + $statesdown / $deldown;							#1/1
477		$flyingsequence .= 'onknees 1,';
478		$flying->{"CON$j"} = $con;
479		# $flying->{"DEL$j"} = 1;
480	}
481	$flyingsequence = "+kneeling, $flyingsequence -kneeling";
482
483	$flying = { %{$flying}, 'N'=>'JumpFly', 'DEL'=> $deldown, 'S'=>$flyingsequence,
484		'DELN'=>100 };
485	$flyingstart = { 'N'=>'JumpStart', 'JUMP2'=>$jumpheight, 'PUSHX2'=>9*16, 'DEL1'=>1,
486		'DEL'=> $deldown, 'S'=>"stand 1,$flyingsequence", 'DELN'=>100 };
487
488	print join( ',', %{$flying}), "\n";
489
490	$jumpkick = { 'N'=>'JumpKick', 'HIT'=>'Fall',
491		'DEL'=> int( $statestotal * 2 / 3 / ( $kickframes + $kneelingframes*2 + 3 ) ),		# 1/1
492		'S'=> '+kneelingkick,kneelingkick n, kneelingkick n, kneelingkick n,-kneelingkick,-kneeling',
493		'HIT'=>'Fall', 'DELN'=>100 };
494
495	$jumppunch = { 'N'=>'JumpPunch', 'HIT'=>'Highhit',
496		'DEL'=> int( $statestotal * 2 / 3 / ( $punchframes + $kneelingframes*2 + 3 ) ),		# 1/1
497		'S'=> '+kneelingpunch,kneelingpunch n, kneelingpunch n, kneelingpunch n,-kneelingpunch,-kneeling',
498		'HIT'=>'Fall', 'DELN'=>100 };
499
500	return ($jump, $jumpfw, $jumpbw, $flying, $flyingstart, $jumpkick, $jumppunch);
501}
502
503
504
505sub WalkingFrames($$$$$)
506{
507	my ( $frameLookup, $frameArray, $preFrames, $distance, $con ) = @_;
508
509	my ( $walkFrames, $totalFrames, $seq, $seq2, $distPerFrame,
510		$walk, $back, $walkNextst, $backNextst,
511		$i, $j, );
512
513	$totalFrames = FindLastFrame( $frameLookup, 'walk' );
514	$walkFrames = $totalFrames - $preFrames;
515
516	if ( $preFrames > 0 ) {
517		$seq = "+walk, walk $preFrames-1";
518		$seq2 = "walk 1-$preFrames, -walk";
519	} else {
520		$seq = "+walk";
521		$seq2 = "-walk";
522	}
523
524	$walk = { 'N'=>'Walk', 'S'=>$seq, 'DEL'=>5, 'CON'=>$con };
525	$back = { 'N'=>'Back', 'S'=>$seq2, 'DEL'=>5, };
526
527	# Add attributes for the 'pre' states.
528
529	for ( $i=1; $i <= $preFrames; ++$i )
530	{
531		$j = $i + 1;
532		$walk->{"CON$i"} = { %{$con}, 'forw' => "Walk $j" };
533		$walk->{"NEXTST$i"} = 'Stand';
534		$back->{"CON$i"} = { %{$con}, 'back' => "Back $j" };
535		$back->{"NEXTST$i"} = 'Stand';
536	}
537
538	# Add attributes for the 'walk' states.
539
540	$walkNextst = $preFrames ? 'Walk ' . ($totalFrames+1) : 'Stand';
541	$backNextst = $preFrames ? 'Back ' . ($totalFrames+1) : 'Stand';
542	$distPerFrame = $distance / $walkFrames;							# 1/1
543
544	print "*** $preFrames $walkFrames $totalFrames $walkNextst $backNextst\n";
545
546	for ( $i=$preFrames+1; $i <= $totalFrames; ++$i )
547	{
548		$j = ($i == $totalFrames) ? $preFrames+1 : $i+1;
549		$walk->{"MOVE$i"} = 4;
550		$walk->{"NEXTST$i"} = $walkNextst;
551		$walk->{"CON$i"} = { %{$con}, 'forw' => "Walk $j" };
552		$back->{"MOVE$i"} = -4;
553		$back->{"NEXTST$i"} = $backNextst;
554		$back->{"CON$i"} = { %{$con}, 'back' => "Back $j" };
555
556		OffsetFrame( $frameArray->[$frameLookup->{"walk$i"}],
557			- ($i-$preFrames-1) * $distPerFrame, 0 );
558	}
559
560	return ( $walk, $back );
561}
562
563
564
565sub TravelingStates( $$$$$$ )
566{
567	my ( $frameLookup, $frameArray, $states, $frameName, $from, $to ) = @_;
568
569	$from = 1 unless $from;
570	unless ( $to )
571	{
572		$to = FindLastFrame( $frameLookup, $frameName );
573		$to += 1 if $frameName eq 'falling';
574	}
575
576	my ( $fromIndex, $toIndex, $fromFrame, $toFrame, $fromOffset, $toOffset,
577		$deltax, $i, $state, $nextst );
578
579	# 1. Calculate the 'deltax' and 'fromOffset'.
580
581	$fromIndex = $frameLookup->{"$frameName$from"};
582	die "couldn't find frame $frameName$from" unless defined $fromIndex;
583	$toIndex = $fromIndex - $from + $to;
584
585	$fromFrame = $frameArray-> [ $fromIndex ];
586	$toFrame = $frameArray-> [ $toIndex ];
587
588	$fromOffset = $fromFrame->{x} + ($fromFrame->{w} >> 1);
589	$toOffset = $toFrame->{x} + ($toFrame->{w} >> 1);
590	$deltax = ( $toOffset - $fromOffset ) / ( $to - $from );	#1/1
591
592	# print "Offsets: $fromOffset $toOffset $deltax\n";
593
594	# 2. Offset every relevant frame.
595	for ( $i=$fromIndex; $i<=$toIndex; ++$i )
596	{
597		# print "Offsetting frame $i by ", - $fromOffset - $deltax * ($i-$fromIndex), "\n";
598		OffsetFrame( $frameArray->[$i],
599			- $fromOffset - $deltax * ($i-$fromIndex), 0 );
600	}
601
602	# 3. Apply deltax to every relevant state.
603	while ( ($i, $state) = each %{$states} )
604	{
605		if ( $state->{F} >= $fromIndex and $state->{F} <= $toIndex )
606		{
607			$nextst = $states->{$state->{NEXTST}};
608			if ( defined($nextst) and $nextst->{F} >= $fromIndex and $nextst->{F} <= $toIndex )
609			{
610				$state->{DELTAX} = $deltax * ($nextst->{F} - $state->{F});
611				# print "Fixing state $i : deltax = ", $state->{DELTAX}, "\n";
612			}
613		}
614	}
615}
616
617
618
619sub FixStates($$)
620{
621	my ( $frameLookup, $states ) = @_;
622
623	my ( $framename, $st, $lastchar, $key, $value, $nextchar, $nextst );
624
625	while (($key, $value) = each %{$states})
626	{
627		$framename = $value->{'F'};
628		unless ( $framename =~/^\d+$/ )
629		{
630			# Convert non-numeric frames to their numeric counterparts.
631			die "Can't find image $framename in frame $key" unless defined $frameLookup->{ $framename };
632			$value->{'F'} = $frameLookup->{ $framename };
633		}
634
635		($st,$lastchar) = $key =~ /(\w+)\s+(\d+)/;
636
637		unless ( defined $value->{'NEXTST'} )
638		{
639			$nextchar = $lastchar + 1;
640			$nextst = "$st $nextchar";
641			unless ( defined $states->{$nextst} ) {
642				# print "Go to Standby after $key\n";
643				$nextst = 'Stand';
644			}
645			$value->{'NEXTST'} = $nextst;
646		}
647
648	}
649}
650
651
652sub FindShorthands($)
653{
654	my ( $states ) = @_;
655
656	my ( $key, $value, $st, $lastchar, %Shorthands );
657
658	while (($key, $value) = each %{$states})
659	{
660		($st,$lastchar) = $key =~ /(\w+)\s+(\d+)/;
661		print "$key has no lastchar" unless defined $lastchar;
662		if ( $lastchar == 1 )
663		{
664			$Shorthands{$st} = $states->{$key};
665		}
666	}
667
668	return %Shorthands;
669}
670
671
672sub CheckStates($$)
673{
674	my ( $fightername, $states ) = @_;
675	my ( $key,$state, $con );
676	my ( $seq,$nextst );
677
678	while (($key, $state) = each %{$states})
679	{
680		die "Bad connection in fighter $fightername to '$state->{NEXTST} from $key!'" unless exists $states->{ $state->{NEXTST} };
681		next unless $state->{CON};
682		$con = $state->{CON};
683
684		while (($seq, $nextst) = each %{$con})
685		{
686			die "Bad connection in fighter $fightername to '$nextst' from $key!" unless exists $states->{$nextst};
687		}
688	}
689}
690
691
692
693
694
695return 1;
696