Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F625224
DataHelper.pl
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
17 KB
Referenced Files
None
Subscribers
None
DataHelper.pl
View Options
#
DataHelper
contains
subroutines
useful
for
loading
a
character's
# frames, and creating his states.
use strict;
require '
FighterStats
.
pl';
=comment
SITUATIONS ARE:
Ready, Stand, Crouch, (Midair = any + character is flying), Falling
SITUATION DEPENDENT EVENTS ARE:
Highhit, Uppercut, Hit, Groinhit, Leghit, Fall
STANDBY EVENTS ARE:
Won, Hurt, Threat, Fun, Turn
________|___Ready___________Block___Stand___________Crouch______________Midair______Falling
Highhit | HighPunched - HighPunched KneelingPunched (...) (...)
Uppercut| Falling - Falling KneelingPunched (...) (...)
Hit | LowPunched - LowPunched KneelingKicked
Groinhit| GroinKicked - GroinKicked KneelingKicked
Leghit | Swept - Swept KneelingKicked
Fall | Falling - Falling KneelingPunched
FRAME MEMBER DESCRIPTION IS:
x int X coordinate offset of the image relative to the character's
anchor
.
y
int
Y
coordinate
offset
of
the
image
relative
to
the
character's anchor.
w int The width of the image.
h int The height of the image.
head array The coordinates of a polygon marking the head within the image, relative to the anchor.
body array The coordinates of a polygon marking the body within the image, relative to the anchor.
legs array The coordinates of a polygon marking the legs within the image, relative to the anchor.
hit array The coordinates of a polygon marking the hit within the image, relative to the anchor.
STATE MEMBER DESCRIPTION IS:
F int The number of the visible frame.
SITU string The situation associated with this state (Ready, Stand, Crouch, Falling)
DEL int The delay before moving to the next state.
NEXTST string The name of the state which follows this state, if none of the CONs is used.
CON hash Connections from this state. The keys are either events or keyboard input.
HIT ? The hit delivered at the beginning of this state.
BLOCK int If true, the character is blocking in his current state.
MOVE int The character's
anchor
should
continously
move
this
much
during
this
state
.
DELTAX
int
The
character's anchor should immediately change by this much after this state.
PUSHX int The character is pushed, with this much momentum upon entering this state.
TURN int If true, the character's
facing
direction
changes
after
this
state
.
JUMP
int
The
character
leaps
into
the
air
,
with
this
much
initial
momentum
upon
entering
this
state
.
DOODAD
string
A
doodad
is
created
at
the
beginning
of
this
state
.
The
string
contains
the
doodad's type and speed.
SOUND string The sound effect associated with this state (if any);
HITSND string The sound effect if the HIT is successful.
MISSSND string The sound effect if the HIT fails.
CODE string This code will be evaled at the beginning of this state.
=cut
# Loads the frame data (x, y, w, h) from the given datafile.
# The path to the datafile is inserted automatically. The frame data will
# be shifted by (-PivotX,-PivotY).
#
# Returns an array of frame data. The first element in the array is
# a dummy entry, the second is the first real entry. This is because
# the first thing in the datafile is a PAL entry.
#
# Example: LoadFrames( "ZOLIDATA.DAT.txt" );
sub LoadFrames ($$$)
{
my ($DataName, $PivotX, $PivotY) = @_;
my (@Frames, $data, $frame);
open DATAFILE, "../characters/$DataName" || die ("Couldn't
open
..
/
characters/
$
DataName
");
$data = '';
while ( read DATAFILE, $data, 16384, length($data) )
{
}
close DATAFILE;
print "
$
DataName
file
is
", length($data), "
bytes
long
.
\n
";
# Insert a dummy first row for the palette entry
eval ("
\@
Frames
=
(
{},
$
data
);
");
die $@ if $@;
foreach $frame (@Frames)
{
OffsetFrame( $frame, -$PivotX, -$PivotY );
}
print "
$
DataName
loaded
,
", scalar @Frames, "
frames
.
\n
";
return @Frames;
}
# Creates a frame lookup from a descriptor array.
# The first parameter is the expected number of frames.
# The descriptor array should have frame base names in even positions,
# and lengths at odd. positions. For example:
# ("
start
", 6, "
stand
", 4, ... )
#
# The routine will return a lookup which will contain the frame's logical
# name as a key, and its physical index as a value. The logical names are
# simply the basename plus a number. The example above would return:
# ("
start1
"=>1, "
start2
"=>2, ..., "
start6
"=>6, "
stand1
"=>6, "
stand2
"=>7, ...)
sub CreateFrameLookup
{
my ($ExpectedCount, @FrameDesc) = @_;
my ($FrameName, $NumFrames);
my ($i, $j);
my (%FrameLookup);
for ( $i=0; $i<scalar @FrameDesc; $i +=2 )
{
$FrameName = $FrameDesc[$i];
$NumFrames = $FrameDesc[$i+1];
for ( $j = 1; $j<=$NumFrames; ++$j )
{
# print "
Frame
", (scalar keys %FrameLookup) + 1, "
is
now
called
$
FrameName
$
j\n
";
$FrameLookup{ "
$
FrameName
$
j
" } = (scalar keys %FrameLookup) + 1;
}
}
if ( $ExpectedCount != scalar keys( %FrameLookup ) )
{
die( "
Expected
number
of
frames
(
$
ExpectedCount
)
doesn't equal the actual number of frames: ".
scalar keys(%FrameLookup) );
}
return %FrameLookup;
}
# Helper function. Finds the last frame with a given name in a frame
# lookup structure. Return the index of the last frame (1-based), or
# 0 if none with the given name were found.
#
# Example: If there are 6 "highpunch" frames (highpunch1 to highpunch6),
# FindLastFrame(\%FrameLookup, "highpunch") returns 6.
sub FindLastFrame($$) {
my ($FrameLookup, $FrameName) = @_;
my ($i) = (1);
while ( exists ${$FrameLookup}{"$FrameName$i"} ) { $i++; }
return $i-1;
}
sub OffsetPolygon($$$)
{
my ($poly, $dx, $dy) = @_;
my ($i, $n);
$n = scalar @{$poly};
for ( $i=0; $i < $n; $i+=2 )
{
$poly->[$i] += $dx;
$poly->[$i+1] += $dy;
}
}
sub MirrorPolygon($)
{
my ($poly) = @_;
my ($i, $n);
$n = scalar @{$poly};
for ( $i=0; $i < $n; $i+=2 )
{
$poly->[$i] = - $poly->[$i];
}
}
sub OffsetFrame($$$) {
my ($frame, $dx, $dy) = @_;
$frame->{'x'} += $dx;
$frame->{'y'} += $dy;
OffsetPolygon( $frame->{head}, $dx, $dy ) if defined ($frame->{head});
OffsetPolygon( $frame->{body}, $dx, $dy ) if defined ($frame->{body});
OffsetPolygon( $frame->{legs}, $dx, $dy ) if defined ($frame->{legs});
OffsetPolygon( $frame->{hit}, $dx, $dy ) if defined ($frame->{hit});
}
# FindLastState returns the last index of a given state.
# For example, if Punch4 is the last in Punch, FindLastState("Punch") is 4.
sub FindLastState($$) {
my ( $States, $StateName ) = @_;
my ( $i ) = ( 1 );
while ( exists ${$States}{ "$StateName $i" } ) { $i++; }
return $i-1;
}
# Translates an abbreviated sequence to a full sequence.
# "-punch" is every punch frame backwards.
# "_punch" is every punch frame except the last one backwards.
# "+punch" is every punch frame forwards.
sub TranslateSequence($$) {
my ($FrameLookup, $Sequence) = @_;
my ($pre, $frame) = $Sequence =~ /^([+-_]{0,1})(\w+)/;
my ($LastFrame) = (FindLastFrame( $FrameLookup, $frame ) );
#$LastFrame = (FindLastFrame( $FrameLookup, "$pre$frame" ) ) if $LastFrame == 0;
#print "Last frame of $frame is $LastFrame.\n";
return "$frame 1-$LastFrame" if ( $pre eq '+' );
return "$frame $LastFrame-1" if ( $pre eq '-' );
return "$frame " . ($LastFrame-1) . "-1" if ( $pre eq '
_
' );
$Sequence =~ s/\sn(-{0,1})/ $LastFrame$1/; # Replace n- with last frame
$Sequence =~ s/-n/-$LastFrame/; # Replace -n with last frame
return $Sequence;
}
sub SetStateData($$$)
{
my ($state, $FrameDesc, $suffix) = @_;
$state->{DEL} = $FrameDesc->{"DEL$suffix"} if defined $FrameDesc->{"DEL$suffix"};
$state->{HIT} = $FrameDesc->{"HIT$suffix"} if defined $FrameDesc->{"HIT$suffix"};
$state->{CON} = $FrameDesc->{"CON$suffix"} if defined $FrameDesc->{"CON$suffix"};
$state->{BLOCK} = $FrameDesc->{"BLOCK$suffix"} if defined $FrameDesc->{"BLOCK$suffix"};
$state->{NEXTST} = $FrameDesc->{"NEXTST$suffix"} if defined $FrameDesc->{"NEXTST$suffix"};
$state->{MOVE} = $FrameDesc->{"MOVE$suffix"} if defined $FrameDesc->{"MOVE$suffix"};
$state->{DELTAX} = $FrameDesc->{"DELTAX$suffix"} if defined $FrameDesc->{"DELTAX$suffix"};
$state->{PUSHX} = $FrameDesc->{"PUSHX$suffix"} if defined $FrameDesc->{"PUSHX$suffix"};
$state->{TURN} = $FrameDesc->{"TURN$suffix"} if defined $FrameDesc->{"TURN$suffix"};
$state->{JUMP} = $FrameDesc->{"JUMP$suffix"} if defined $FrameDesc->{"JUMP$suffix"};
$state->{SITU} = $FrameDesc->{"SITU$suffix"} if defined $FrameDesc->{"SITU$suffix"};
$state->{DOODAD} = $FrameDesc->{"DOODAD$suffix"} if defined $FrameDesc->{"DOODAD$suffix"};
$state->{SOUND} = $FrameDesc->{"SOUND$suffix"} if defined $FrameDesc->{"SOUND$suffix"};
$state->{CODE} = $FrameDesc->{"CODE$suffix"} if defined $FrameDesc->{"CODE$suffix"};
}
# Adds a sequence to the end of a state
# Sequences are: e.g. "throw 10-14, throw 16, throw 14-10"
# Each piece of the sequence will have $Delay delay.
sub AddStates($$$) {
my ( $States, $Frames, $FrameDesc ) = @_;
my ( $StateName, $SequenceString, $LastState, $i, $sloop, $s, @Sequences );
my ( $from, $to, $frame, $state );
$StateName = $FrameDesc->{'
N
'};
$SequenceString = $FrameDesc->{'
S
'};
$FrameDesc->{SITU} = '
Stand
' unless defined $FrameDesc->{SITU};
$LastState = FindLastState($States,$StateName)+1;
@Sequences = split ( /\s*,\s*/, $SequenceString );
for ( $sloop = 0; $sloop < scalar @Sequences; ++$sloop )
{
$s = TranslateSequence( $Frames, $Sequences[$sloop] );
#print "Sequence is $s\n";
if ( $s =~ /^\s*(\w+)\s+(\d+)-(\d+)\s*$/ )
{
# Sequence is '
<
frame
>
<
from>-<to>'
$frame = $1;
$from = $2;
$to = $3;
}
elsif ( $s =~ /^\s*(\w+)\s+(\d+)\s*$/ )
{
# Sequence is '
<
frame
>
<
number>'
$frame = $1;
$from = $to = $2;
}
else
{
die "Sequence '
$
s' incorrect.\n";
}
$i = $from;
while (1)
{
die "Error: Frame $frame$i doesn't
exist
.
\n
"
unless defined ${$Frames}{"
$
frame$i
"};
$state = { 'F'=>"
$
frame$i
" };
SetStateData( $state, $FrameDesc, '' );
SetStateData( $state, $FrameDesc, $LastState );
if ( ( $sloop == scalar @Sequences -1 ) and ( $i == $to ) )
{
SetStateData( $state, $FrameDesc, 'N' );
}
$States->{"
$
StateName
$
LastState
"} = $state;
# print "
Added
state
'$StateName $LastState'
as
frame
'$frame$i'
,
delay
$
Delay
\n
";
$LastState++;
if ( $from < $to )
{
$i++;
last if $i > $to;
}
else
{
$i--;
last if $i < $to;
}
}
}
}
sub BlockStates($$)
{
my ( $frames, $del) = @_;
my ( $retval, $i );
$retval = { 'N'=>'Block', 'DEL'=>$del, 'S'=>'+block', "
BLOCK
$
frames
"=>1 };
for ($i = 1; $i <= $frames; ++$i )
{
$retval->{"
NEXTST
$
i
"} = "
Block
" . ($i-1);
$retval->{"
CON
$
i
"} = { 'block'=> "
Block
" . ($i+1) };
}
$retval->{'NEXTST1'} = 'Stand';
$retval->{"
CON
$
frames
"} = { 'block'=> "
Block
" . $frames };
return $retval;
}
sub KneelingStates($$$$)
{
my ( $frames, $frames2, $del, $con ) = @_;
my ( $retval, $retval2, $i, $j );
$retval = { 'N'=>'Kneeling', 'DEL'=> $del, 'S' => '+kneeling', 'SITU'=>'Crouch' };
for ( $i = 1; $i <= $frames; ++$i )
{
$retval->{"
NEXTST
$
i
"} = "
Kneeling
" . ($i-1);
$retval->{"
CON
$
i
"} = { 'down' => "
Kneeling
" . ($i+1) };
}
$retval->{'NEXTST1'} = 'Stand';
$retval->{"
CON
$
frames
"} = { 'down' => "
Onknees
" };
$retval2 = { 'N'=>'Onknees', 'DEL'=>$del, 'S' => '+onknees,-onknees', 'SITU'=>'Crouch',
'NEXTST' => "
Kneeling
$
frames
" };
$frames2 *= 2;
for ( $i = 1; $i <= $frames2; ++$i )
{
$j = ($i % $frames2) + 1;
$retval2->{"
CON
$
i
"} = { %{$con}, 'down'=>"
Onknees
$
j
", 'forw'=>"
Onknees
$
j
", 'back'=>"
Onknees
$
j
" };
}
return ($retval, $retval2);
}
sub JumpStates($$)
{
my ( $frames, $con ) = @_;
my ( $kneelingframes, $onkneesframes,
$kickframes, $punchframes ) = (
FindLastFrame( $frames, 'kneeling' ),
FindLastFrame( $frames, 'onknees' ),
FindLastFrame( $frames, 'kneelingkick' ),
FindLastFrame( $frames, 'kneelingpunch' ) );
my ( $jump ) = 120;
my ( $i, $j, $statestotal, $statesdown, $statesknees, $deldown,
$jumpfw, $jumpbw, $flying, $flyingsequence, $jumpkick, $jumppunch );
# The jump's first part is going down on knees, second part is
# on knees, third part is getting up.
if ( $::DELMULTIPLIER )
{
$statestotal = $jump * 2 / 3 / $::DELMULTIPLIER; # 1/1
}
else
{
$statestotal = $jump * 2 / 3;
}
$statesdown = $statestotal / 4;
$deldown = int($statesdown / $kneelingframes + 0.1); # 1/1
$statesdown = $deldown * $kneelingframes;
$statesknees = $statestotal - $statesdown * 2;
$jump = { 'N'=>'Jump', 'DEL'=> $deldown, 'S'=>'kneeling 1-2, kneeling 1',
'JUMPN'=>$jump, NEXTSTN=>'JumpFly', 'SOUND1'=>'slip4.voc', };
$jumpfw = { %{$jump}, 'N'=>'JumpFW', 'PUSHX3'=>18*16 };
$jumpbw = { %{$jump}, 'N'=>'JumpBW', 'PUSHX3'=>-9*16 };
$flyingsequence = '';
$flying = {};
for ( $i = 0; $i < $statesknees / $deldown; ++$i ) #1/1
{
$j = $i + $statesdown / $deldown; #1/1
$flyingsequence .= 'onknees 1,';
$flying->{"
CON
$
j
"} = $con;
# $flying->{"
DEL
$
j
"} = 1;
}
$flyingsequence = "
+
kneeling
,
$
flyingsequence
-
kneeling
";
$flying = { %{$flying}, 'N'=>'JumpFly', 'DEL'=> $deldown, 'S'=>$flyingsequence,
'DELN'=>100 };
print join( ',', %{$flying}), "
\n
";
$jumpkick = { 'N'=>'JumpKick', 'HIT'=>'Fall',
'DEL'=> int( $statestotal * 2 / 3 / ( $kickframes + $kneelingframes*2 + 3 ) ), # 1/1
'S'=> '+kneelingkick,kneelingkick n, kneelingkick n, kneelingkick n,-kneelingkick,-kneeling',
'HIT'=>'Fall', 'DELN'=>100 };
$jumppunch = { 'N'=>'JumpPunch', 'HIT'=>'Highhit',
'DEL'=> int( $statestotal * 2 / 3 / ( $punchframes + $kneelingframes*2 + 3 ) ), # 1/1
'S'=> '+kneelingpunch,kneelingpunch n, kneelingpunch n, kneelingpunch n,-kneelingpunch,-kneeling',
'HIT'=>'Fall', 'DELN'=>100 };
return ($jump, $jumpfw, $jumpbw, $flying, $jumpkick, $jumppunch);
}
sub WalkingFrames($$$$$)
{
my ( $frameLookup, $frameArray, $preFrames, $distance, $con ) = @_;
my ( $walkFrames, $totalFrames, $seq, $seq2, $distPerFrame,
$walk, $back, $walkNextst, $backNextst,
$i, $j, );
$totalFrames = FindLastFrame( $frameLookup, 'walk' );
$walkFrames = $totalFrames - $preFrames;
if ( $preFrames > 0 ) {
$seq = "
+
walk
,
walk
$
preFrames
-
1
";
$seq2 = "
walk
1
-
$
preFrames
,
-
walk
";
} else {
$seq = "
+
walk
";
$seq2 = "
-
walk
";
}
$walk = { 'N'=>'Walk', 'S'=>$seq, 'DEL'=>5, 'CON'=>$con };
$back = { 'N'=>'Back', 'S'=>$seq2, 'DEL'=>5, };
# Add attributes for the 'pre' states.
for ( $i=1; $i <= $preFrames; ++$i )
{
$j = $i + 1;
$walk->{"
CON
$
i
"} = { %{$con}, 'forw' => "
Walk
$
j
" };
$walk->{"
NEXTST
$
i
"} = 'Stand';
$back->{"
CON
$
i
"} = { %{$con}, 'back' => "
Back
$
j
" };
$back->{"
NEXTST
$
i
"} = 'Stand';
}
# Add attributes for the 'walk' states.
$walkNextst = $preFrames ? 'Walk ' . ($totalFrames+1) : 'Stand';
$backNextst = $preFrames ? 'Back ' . ($totalFrames+1) : 'Stand';
$distPerFrame = $distance / $walkFrames; # 1/1
print "
***
$
preFrames
$
walkFrames
$
totalFrames
$
walkNextst
$
backNextst\n
";
for ( $i=$preFrames+1; $i <= $totalFrames; ++$i )
{
$j = ($i == $totalFrames) ? $preFrames+1 : $i+1;
$walk->{"
MOVE
$
i
"} = 4;
$walk->{"
NEXTST
$
i
"} = $walkNextst;
$walk->{"
CON
$
i
"} = { %{$con}, 'forw' => "
Walk
$
j
" };
$back->{"
MOVE
$
i
"} = -4;
$back->{"
NEXTST
$
i
"} = $backNextst;
$back->{"
CON
$
i
"} = { %{$con}, 'back' => "
Back
$
j
" };
OffsetFrame( $frameArray->[$frameLookup->{"
walk$i
"}],
- ($i-$preFrames-1) * $distPerFrame, 0 );
}
return ( $walk, $back );
}
sub TravelingStates( $$$$$$ )
{
my ( $frameLookup, $frameArray, $states, $frameName, $from, $to ) = @_;
$from = 1 unless $from;
$to = FindLastFrame( $frameLookup, $frameName ) unless $to;
my ( $fromIndex, $toIndex, $fromFrame, $toFrame, $fromOffset, $toOffset,
$deltax, $i, $state, $nextst );
# 1. Calculate the 'deltax' and 'fromOffset'.
$fromIndex = $frameLookup->{"
$
frameName$from
"};
die "
couldn't find frame $frameName$from" unless defined $fromIndex;
$toIndex = $fromIndex - $from + $to;
$fromFrame = $frameArray-> [ $fromIndex ];
$toFrame = $frameArray-> [ $toIndex ];
$fromOffset = $fromFrame->{x} + ($fromFrame->{w} >> 1);
$toOffset = $toFrame->{x} + ($toFrame->{w} >> 1);
$deltax = ( $toOffset - $fromOffset ) / ( $to - $from ); #1/1
# print "Offsets: $fromOffset $toOffset $deltax\n";
# 2. Offset every relevant frame.
for ( $i=$fromIndex; $i<=$toIndex; ++$i )
{
# print "Offsetting frame $i by ", - $fromOffset - $deltax * ($i-$fromIndex), "\n";
OffsetFrame( $frameArray->[$i],
- $fromOffset - $deltax * ($i-$fromIndex), 0 );
}
# 3. Apply deltax to every relevant state.
while ( ($i, $state) = each %{$states} )
{
if ( $state->{F} >= $fromIndex and $state->{F} <= $toIndex )
{
$nextst = $states->{$state->{NEXTST}};
if ( $nextst->{F} >= $fromIndex and $nextst->{F} <= $toIndex )
{
$state->{DELTAX} = $deltax * ($nextst->{F} - $state->{F});
# print "Fixing state $i : deltax = ", $state->{DELTAX}, "\n";
}
}
}
}
sub FixStates($$)
{
my ( $frameLookup, $states ) = @_;
my ( $framename, $st, $lastchar, $key, $value, $nextchar, $nextst );
while (($key, $value) = each %{$states})
{
$framename = $value->{'
F
'};
unless ( $framename =~/^\d+$/ )
{
# Convert non-numeric frames to their numeric counterparts.
die "Can't
find
image
$
framename
in
frame
$
key
"
unless
defined
$
frameLookup->
{
$
framename
};
$
value->
{
'F'
}
=
$
frameLookup->
{
$
framename
};
}
(
$
st
,
$
lastchar
)
=
$
key
=~
/
(
\w+
)
\s+
(
\d+
)
/
;
unless
(
defined
$
value->
{
'NEXTST'
}
)
{
$
nextchar
=
$
lastchar
+
1
;
$
nextst
=
"$st $nextchar"
;
unless
(
defined
$
states->
{
$
nextst
}
)
{
#
print
"Go to Standby after $key\n"
;
$
nextst
=
'Stand'
;
}
$
value->
{
'NEXTST'
}
=
$
nextst
;
}
}
}
sub
FindShorthands
(
$
)
{
my
(
$
states
)
=
@
_
;
my
(
$
key
,
$
value
,
$
st
,
$
lastchar
,
%Shorthands );
while
((
$
key
,
$
value
)
=
each
%{$states})
{
(
$
st
,
$
lastchar
)
=
$
key
=~
/
(
\w+
)
\s+
(
\d+
)
/
;
print
"$key has no lastchar"
unless
defined
$
lastchar
;
if
(
$
lastchar
==
1
)
{
$
Shorthands
{
$
st
}
=
$
states->
{
$
key
};
}
}
return
%Shorthands;
}
return
1
;
File Metadata
Details
Attached
Mime Type
text/x-ruby
Expires
Sat, Nov 14, 11:13 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
135995
Default Alt Text
DataHelper.pl (17 KB)
Attached To
Mode
R76 OpenMortal
Attached
Detach File
Event Timeline