Page MenuHomePhabricator (Chris)

No OneTemporary

Size
99 KB
Referenced Files
None
Subscribers
None
This document is not UTF8. It was detected as ISO-8859-1 (Latin 1) and converted to UTF8 for display.
diff --git a/data/script/Backend.pl b/data/script/Backend.pl
index a16f287..eab5aac 100644
--- a/data/script/Backend.pl
+++ b/data/script/Backend.pl
@@ -1,765 +1,772 @@
#!/usr/bin/perl -w
# PERL backend for the game
=comment
This is the main perl backend for Mortal Szombat. It loads the other files,
compiles characters, creates shared variables.
1. FIGHTER ACTIONS
1.1. BASIC MOVEMENT 1.2. OFFENSIVE MOVES 1.3. HURT MOVES
Start KneelingPunch Falling
Stand KneelingKick Laying
Walk KneelingUppercut Getup
Back HighPunch HighPunched
Turn LowPunch LowPunched
Hurt HighKick GroinKicked
Block LowKick KneelingPunched
Kneeling Sweep KneelingKicked
Onknees GroinKick Thrown
Jump KneeKick Dead
JumpFW Elbow
JumpBW Grenade
Fun Uppercut
Threat Throw
[fighter-specific stuff]
=cut
=comment
MAIN CONSTANTS
=cut
+BEGIN {
+ use Cwd;
+ our $directory = cwd;
+}
+
+use lib $directory;
+
sub InitMainConstants($$)
{
my ($wide, $numplayers) = @_;
$MAXPLAYERS = 4; # The maximal number of players in the game
$WIDE = $wide;
$NUMPLAYERS = $numplayers; # Number of players in the game
$GAMEBITS = 3; # Number of oversampling bits in coordinates
$GAMEBITS2 = 1 << $GAMEBITS; # 2^(oversampling)
$SCRWIDTH = $wide ? 800 : 640; # The physical horizontal screen resolution
$SCRHEIGHT = 480; # The physical vertical screen resolution
$SCRWIDTH2 = ($SCRWIDTH << $GAMEBITS); # The logical horizontal screen resolution
$SCRHEIGHT2 = ($SCRHEIGHT << $GAMEBITS); # The logical vertical screen resolution
$MOVEMARGIN2= 50 << $GAMEBITS; # The logical border for fighters.
$BGWIDTH2 = 1920 << $GAMEBITS; # The logical background width
$BGHEIGHT2 = 480 << $GAMEBITS; # The logical background height
$GROUND2 = 160 << $GAMEBITS; # The logical ground level.
$DELMULTIPLIER = 1; # DEL values in states are multiplied by this.
$MAXCOMBO = 5; # Maximum combo count.
$BgMax = $BGWIDTH2 - $SCRWIDTH2; # The logical maximum camera position
$BgSpeed = 0; # The current speed of the background movement
$BgPosition = $BgMax >> 1; # The logical camera position
$BgScrollEnabled = 1; # Can the background scroll?
$HitPointScale = 10; # Scale the hit points by this.
$NextDoodad = 0; # The next doodad to process
$Debug = 0;
}
InitMainConstants( 0, 2 );
require 'PlayerInput.pl';
require 'Fighter.pl';
require 'FighterStats.pl';
require 'Doodad.pl';
require 'Keys.pl';
require 'State.pl';
require 'Translate.pl';
require 'Rewind.pl';
=comment
MAIN OBJECTS
=cut
sub CreateFighters()
{
my ( $i, $fighter );
@Fighters = ();
for ( $i=0; $i<$MAXPLAYERS; ++$i )
{
$fighter = Fighter->new();
$fighter->{NUMBER} = $i;
$fighter->{OTHER} = $fighter;
$Fighters[$i] = $fighter;
}
$Fighters[1]->{OTHER} = $Fighters[0];
$Fighters[0]->{OTHER} = $Fighters[1];
}
CreateFighters();
CreatePlayerInputs();
=comment
VARIABLES FOR DRAWING THE SCENE
=cut
$gametick = 0; # Number of Advance()'s called since the start.
$p1x = 10; # Player 1 X position
$p1y = 100; # Player 1 Y position
$p1f = 10; # Player 1 frame
$p1h = 100; # Player 1 HP (the displayed bar)
$p1hreal = 100; # Player 1 real HP (actual value, not the displayed bar)
$p2x = 400; # Player 2 X position
$p2y = 100; # Player 2 Y position
$p2f = -40; # Player 2 frame. Negative means flipped.
$p2h = 100; # Player 2 HP
$p2hreal = 100; # Player 2 real HP (actual value, not the displayed bar)
$bgx = 0; # Background X position
$bgy = 0; # Background Y position
$over = 0; # Is the game over? Used to end the game / player selection.
$ko = 0; # Is one fighter knocked down? Used for instant replay, to capture the moment of the KO. Also disables player input / connections
$doodad_x = -1; # Doodad X position
$doodad_y = -1; # Doodad Y position
$doodad_t = -1; # Doodad type
$doodad_f = -1; # Doodad frame number
$doodad_dir = 0; # The direction of the doodad; 1: normal; -1: flipped
$doodad_gfxowner = -1; # 0: first player; 1: second player; -1: Global doodad
$doodad_text = ''; # The text of type 0 doodads.
=comment
ResetGame clears 'most' game variables. It should not be called by the
frontend directly, instead it is used by the various starting functions,
e.g. "SelectStart" or "JudgementStart".
=cut
sub ResetGame($$$)
{
my ($bgposition, $wide, $numplayers) = @_;
InitMainConstants( $wide, $numplayers );
$gametick = 0;
$over = 0;
$ko = 0;
$bgx = 0;
$bgy = 0;
$BgSpeed = 0;
$BgPosition = $bgposition;
$BgScrollEnabled = $bgposition != 0;
$ActiveFighters = $numplayers; # Number of fighters not in the Win2 / Dead states.
$ActiveTeams = $numplayers; # Number of teams not in the Win2 / Dead states or have more team members.
$OverTimer = 0;
$JudgementMode = 0;
$Debug = 0;
@Doodads = ();
@Sounds = ();
ResetEarthquake();
ResetRewind();
my ($fighter, $input);
foreach $fighter (@Fighters)
{
$fighter->Reset if $fighter->{OK};
$fighter->{TEAMSIZE} = 1;
}
foreach $input (@Inputs)
{
$input->Reset();
}
}
=comment
JUDGEMENT METHODS
=cut
sub JudgementStart($)
{
ResetGame(0, 0, 2);
$JudgementMode = 1;
($JudgementWinner) = @_;
my ($i);
for ( $i=0; $i<$NUMPLAYERS; ++$i )
{
$Fighters[$i]->{X} = ($JudgementWinner!=$i ? 150 : 520 ) * $GAMEBITS2;
$Fighters[$i]->{DIR} = ($JudgementWinner!=$i ? 1 : -1 );
$Fighters[$i]->{NEXTST} = 'Stand';
$Fighters[$i]->Update();
}
}
=comment
PLAYER SELECTION METHODS
=cut
sub SelectStart($)
{
my ( $numplayers ) = @_;
ResetGame(0,0,$numplayers);
my ( $i, $fighter);
$ActiveTeams = 10000; # Make sure we don't trigger an accidental 'Won' event...
if ( 2 == $NUMPLAYERS )
{
for ( $i=0; $i<$NUMPLAYERS; ++$i )
{
if ($Fighters[$i]->{OK})
{
$Fighters[$i]->{X} = (100 + 440*$i) * $GAMEBITS2;
$Fighters[$i]->{NEXTST} = 'Stand';
$Fighters[$i]->{DIR} = $i ? -1 : 1;
$Fighters[$i]->Update();
}
}
}
else
{
foreach $fighter (@Fighters)
{
next unless $fighter->{OK};
last if $fighter->{NUMBER} == $NUMPLAYERS;
$fighter->{X} = $MOVEMARGIN2 + $SCRWIDTH2 / $NUMPLAYERS * $fighter->{NUMBER};
$fighter->{DIR} = 1;
$fighter->{NEXTST} = 'Start';
$fighter->Update();
}
}
}
sub SetPlayerNumber
{
my ($player, $fighterenum) = @_;
my ($f);
DeleteDoodadsOf( $player );
$f = $Fighters[$player];
return unless defined $f;
$f->Reset($fighterenum);
$f->{NEXTST} = 'Stand';
$f->Update();
GetFighterStats($fighterenum);
$::PlayerName = $::Name;
$over = 0;
$OverTimer = 0;
if ( 2 != $NUMPLAYERS )
{
$f->{X} = $MOVEMARGIN2 + $SCRWIDTH2 / $NUMPLAYERS * $f->{NUMBER};
$f->{DIR} = 1;
}
}
sub PlayerSelected
{
my ($number) = @_;
my ($f) = $Fighters[$number];
$f->{NEXTST} = 'Stand';
$f->Update();
$f->Event( 'Won' );
$f->Update();
$over = 0;
$OverTimer = 1;
}
=comment
EARTHQUAKE RELATED METHODS
=cut
@QUAKEOFFSET = ( 0, 6, 11, 15,
16, 15, 11, 6,
0, -6,-11,-15,
-16,-15,-11, -6, 0, 6 );
sub ResetEarthquake
{
$QuakeAmplitude = 0;
$QuakeOffset = 0;
$QuakeX = 0;
$QuakeY = 0;
}
sub AddEarthquake
{
my ($amplitude) = @_;
$QuakeAmplitude += $amplitude;
$QuakeAmplitude = 20 if ( $QuakeAmplitude > 20 );
}
sub AdvanceEarthquake
{
if ( $QuakeAmplitude <= 0.2 )
{
$QuakeAmplitude = $QuakeX = $QuakeY = 0;
return;
}
$QuakeAmplitude -= $QuakeAmplitude / 30 + 0.1;
$QuakeOffset = ( $QuakeOffset + 1 ) % 16;
$QuakeX = $QUAKEOFFSET[$QuakeOffset] * $QuakeAmplitude / 16;
$QuakeY = $QUAKEOFFSET[$QuakeOffset + 1] * $QuakeAmplitude / 16; # 1/1
$bgx -= $QuakeX;
$bgy -= $QuakeY;
$p1x += $QuakeX;
$p1y += $QuakeY;
$p2x += $QuakeX;
$p2y += $QuakeY;
$p3x += $QuakeX;
$p3y += $QuakeY;
$p4x += $QuakeX;
$p4y += $QuakeY;
# Do not quake doodads for now.
}
=comment
GAME BACKEND METHODS
=cut
sub GameStart($$$$$)
{
my ( $MaxHP, $numplayers, $teamsize, $wide, $debug ) = @_;
ResetGame( $BgMax >> 1, $wide, $numplayers );
$::MaxHP = $MaxHP;
$bgx = ( $SCRWIDTH2 - $BGWIDTH2) >> 1;
$bgy = ( $SCRHEIGHT2 - $BGHEIGHT2 ) >> 1;
$BgScrollEnabled = 1;
$HitPointScale = 1000 / $MaxHP; # 1/1
$Debug = $debug;
my ($fighter);
foreach $fighter (@Fighters)
{
$fighter->{TEAMSIZE} = $teamsize;
$fighter->{HP} = $MaxHP;
}
$p1h = $p2h = 0;
}
=comment
NextTeamMember releases the next team member of the given team. This method
is called by the frontend when a fighter dies in team mode. The new fighter
will be released someplace "safe". The team size of the given player will
be decremented.
Input parameters:
$player 0 or 1, the player who has his current fighter replaced
$fighterenum The new fighter who will replace the current figther.
=cut
sub NextTeamMember($$)
{
my ($player, $fighterenum) = @_;
my ($fighter) = $Fighters[$player];
my ($otherx, $oldx);
-- $fighter->{TEAMSIZE};
$oldx = $fighter->{X};
SetPlayerNumber( $player, $fighterenum );
$fighter->{HP} = $::MaxHP;
$over = 0;
$ko = 0;
$OverTimer = 0;
if ( $NUMPLAYERS == 2 )
{
$otherx = $Fighters[1-$fighter->{NUMBER}]->{X};
if ( $otherx < $BGWIDTH2 / 2 )
{
$fighter->{X} = $otherx + $SCRWIDTH2 ;#- $MOVEMARGIN2 * 6;
$fighter->{DIR} = -1;
}
else
{
$fighter->{X} = $otherx - $SCRWIDTH2 ;#+ $MOVEMARGIN2 * 6;
$fighter->{DIR} = +1;
}
if ( abs( $fighter->{X} - $oldx ) > $SCRWIDTH2 / 2 )
{
$fighter->{DELPENALTY} = 100;
}
}
else
{
if ($BgPosition < $BgMax2/2)
{
$fighter->{X} = $BgPosition + $SCRWIDTH2 + $MOVEMARGIN2;
$fighter->{DIR} = -1;
}
else
{
$fighter->{X} = $BgPosition - $MOVEMARGIN2;
$fighter->{DIR} = 1;
}
}
$fighter->{BOUNDSCHECK} = 0;
$fighter->{NEXTST} = 'JumpStart';
$fighter->Update();
}
=comment
Takes a Fighter object, and returns the data relevant for the frontend.
Parameters:
$fighter The fighter which is to be converted to frontend data.
$hp The displayed hit points
Returns:
$x The physical X coordinate of the fighter
$y The physical Y coordinate of the fighter
$fnum The frame number of the fighter. A negative sign means the
fighter is flipped horizontally (facing left)
$hp The displayed hit points
$hpreal The real HP of the given player
=cut
sub GetFighterData($$)
{
my ($fighter, $hp) = @_;
my ($x, $y, $fnum, $f);
$fnum = $fighter->{FR};
$f = $fighter->{FRAMES}->[$fnum];
$x = $fighter->{X} / $GAMEBITS2 + $f->{'x'};
$y = $fighter->{Y} / $GAMEBITS2 + $f->{'y'};
if ($fighter->{DIR}<0)
{
$fnum = -$fnum;
$x -= $f->{x}*2 + $f->{w};
}
$phTarget = $fighter->{HP}*$HitPointScale;
if ( $hp < $phTarget ) { $hp += 5; }
if ( $hp > $phTarget ) { $hp -= 3; }
$hp = $phTarget if abs($hp - $phTarget) < 3;
$hp = 0 if $hp < 0;
return ($x, $y, $fnum, $hp, $fighter->{HP});
}
sub GetNextDoodadData
{
if ( $NextDoodad >= scalar @Doodads )
{
$doodad_x = $doodad_y = $doodad_t = $doodad_f = $doodad_dir = $doodad_gfxowner = -1;
$doodad_text = '';
return;
}
my ($doodad) = $Doodads[$NextDoodad];
$doodad_x = $doodad->{POS}->[0] / $GAMEBITS2 - $bgx;
$doodad_y = $doodad->{POS}->[1] / $GAMEBITS2 - $bgy;
$doodad_t = $doodad->{T};
$doodad_f = $doodad->{F};
$doodad_dir = $doodad->{DIR};
$doodad_gfxowner = $doodad->{GFXOWNER};
$doodad_text = $doodad->{TEXT};
if ($doodad_gfxowner >=0 )
{
$doodad_x += $Fighters[$doodad_gfxowner]->{FRAMES}->[$doodad_f]->{'x'} * $doodad_dir;
$doodad_y += $Fighters[$doodad_gfxowner]->{FRAMES}->[$doodad_f]->{'y'};
}
++$NextDoodad;
}
sub UpdateDoodads
{
my ($i, $j, $doodad);
for ($i=0; $i<scalar @Doodads; ++$i)
{
$doodad = $Doodads[$i];
$j = Doodad::UpdateDoodad( $doodad );
if ( $j )
{
# Remove this Doodad
splice @Doodads, $i, 1;
--$i;
}
}
}
sub DeleteDoodadsOf($)
{
my ($owner) = @_;
my ($i, $doodad);
for ($i=0; $i<scalar @Doodads; ++$i)
{
$doodad = $Doodads[$i];
if ( $doodad->{OWNER} == $owner )
{
# Remove this Doodad
splice @Doodads, $i, 1;
--$i;
}
}
}
sub GetNextSoundData()
{
# print "GetSoundData: ", scalar @Sounds, join ' ,', @Sounds, "\n";
$sound = pop @Sounds;
$sound = '' unless defined $sound;
}
sub DoFighterHitEvent($$$)
{
my ($fighter, $other, $hit) = @_;
$fighter->HitEvent( $other, $other->GetCurrentState()->{HIT}, $hit );
}
sub DoFighterEvents($)
{
my ($fighter) = @_;
if ( $JudgementMode )
{
if ( $fighter->{NUMBER} == $JudgementWinner )
{
$fighter->Event("Won");
}
else
{
$fighter->Event("Hurt");
}
return;
}
if ( $NUMPLAYERS ==2 and $fighter->IsBackTurned )
{
$fighter->Event("Turn");
}
if ( $ActiveTeams <= 1 )
{
$fighter->Event("Won");
}
if ( $fighter->{IDLE} > 150 )
{
my ($r) = $fighter->QuasiRandom( 353 );
$fighter->Event("Fun") if $r==1;
$fighter->Event("Threat") if $r==2;
}
}
sub GameAdvance
{
my ( @hits, @playerhit, $i, $j, $fighter, $input);
$gametick += 1;
$NextDoodad = 0;
$NextSound = 0;
# 1. ADVANCE THE PLAYERS
for ( $i=0; $i<$NUMPLAYERS; ++$i ) {
$input = $Inputs[$i];
$fighter = $Fighters[$i];
$input->Advance();
$fighter->Advance( $input ) if $fighter->{OK};
$hit[$i] = 0;
}
@hits = ();
for ( $i=0; $i<$NUMPLAYERS; ++$i ) {
push @hits, ( $Fighters[$i]->CheckHit() );
}
# 2. Events come here
foreach $hit (@hits) {
print STDERR "hits: $hit->[0] $hit->[1] $hit->[2]\n";
DoFighterHitEvent( $hit->[1], $hit->[0], $hit->[2] );
}
for ( $i=0; $i<$NUMPLAYERS; ++$i ) {
$fighter = $Fighters[$i];
DoFighterEvents( $fighter ) if $fighter->{OK};
}
UpdateDoodads();
for ( $i=0; $i<$NUMPLAYERS; ++$i ) {
$fighter = $Fighters[$i];
$fighter->Update() if $fighter->{OK};
}
if ( $OverTimer == 0 and $ActiveTeams <= 0 )
{
$OverTimer = 1;
}
elsif ( $OverTimer > 0 )
{
$OverTimer++;
$over = 1 if ( $OverTimer > 200 )
}
# 3. DO THE BACKGROUND SCROLLING THING
if ( $BgScrollEnabled )
{
$BgOpt = 0;
for ( $i=0; $i<$NUMPLAYERS; ++$i ) { $BgOpt += $Fighters[$i]->{X}; }
$BgOpt /= $NUMPLAYERS; # 1/1 Stupid kdevelop syntax hightlight.
if ( ($BgOpt - $BgSpeed*50 - $BgPosition) > ($SCRWIDTH2 / 2) ) { $BgSpeed++; }
if ( ($BgOpt - $BgSpeed*50 - $BgPosition) < ($SCRWIDTH2 / 2) ) { $BgSpeed--; }
$BgPosition+=$BgSpeed;
if ($BgPosition<0) { $BgPosition = $BgSpeed = 0; }
if ($BgPosition>$BgMax) { $BgPosition = $BgMax; $BgSpeed = 0; }
# print "Pos:$BgPosition\tOpt:$BgOpt\tSpd:$BgSpeed\t";
}
# 4. SET GLOBAL VARIABLES FOR THE C++ DRAWING TO READ
($p1x, $p1y, $p1f, $p1h, $p1hreal) = GetFighterData( $Fighters[0], $p1h );
($p2x, $p2y, $p2f, $p2h, $p2hreal) = GetFighterData( $Fighters[1], $p2h ) if ($NUMPLAYERS >= 2);
($p3x, $p3y, $p3f, $p3h, $p3hreal) = GetFighterData( $Fighters[2], $p3h ) if ($NUMPLAYERS >= 3);
($p4x, $p4y, $p4f, $p3h, $p4hreal) = GetFighterData( $Fighters[3], $p4h ) if ($NUMPLAYERS >= 4);
$bgx = $BgPosition >> $GAMEBITS;
$p1x -= $bgx;
$p2x -= $bgx;
$p3x -= $bgx;
$p4x -= $bgx;
$bgy = 0;
AdvanceEarthquake();
# 5. DEBUG POLYGONS
if ( $Debug )
{
$fr1 = $Fighter1->{FRAMES}->[ $Fighter1->{FR} ];
@p1head = @{ $fr1->{head} };
MirrorPolygon( \@p1head ) if $Fighter1->{DIR} < 0;
OffsetPolygon( \@p1head, $Fighter1->{X} / $GAMEBITS2 - $bgx, $Fighter1->{Y} / $GAMEBITS2 - $bgy );
@p1body = @{ $fr1->{body} };
MirrorPolygon( \@p1body ) if $Fighter1->{DIR} < 0;
OffsetPolygon( \@p1body, $Fighter1->{X} / $GAMEBITS2 - $bgx, $Fighter1->{Y} / $GAMEBITS2 - $bgy );
@p1legs = @{ $fr1->{legs} };
MirrorPolygon( \@p1legs) if $Fighter1->{DIR} < 0;
OffsetPolygon( \@p1legs, $Fighter1->{X} / $GAMEBITS2 - $bgx, $Fighter1->{Y} / $GAMEBITS2 - $bgy );
if ( defined $fr1->{hit} )
{
@p1hit = @{ $fr1->{hit} };
MirrorPolygon( \@p1hit ) if $Fighter1->{DIR} < 0;
OffsetPolygon( \@p1hit, $Fighter1->{X} / $GAMEBITS2 - $bgx, $Fighter1->{Y} / $GAMEBITS2 - $bgy );
}
else
{
undef @p1hit;
}
$fr2 = $Fighter2->{FRAMES}->[ $Fighter2->{FR} ];
@p2head = @{ $fr2->{head} };
MirrorPolygon( \@p2head ) if $Fighter2->{DIR} < 0;
OffsetPolygon( \@p2head, $Fighter2->{X} / $GAMEBITS2 - $bgx, $Fighter2->{Y} / $GAMEBITS2 - $bgy );
@p2body = @{ $fr2->{body} };
MirrorPolygon( \@p2body ) if $Fighter2->{DIR} < 0;
OffsetPolygon( \@p2body, $Fighter2->{X} / $GAMEBITS2 - $bgx, $Fighter2->{Y} / $GAMEBITS2 - $bgy );
@p2legs = @{ $fr2->{legs} };
MirrorPolygon( \@p2legs) if $Fighter2->{DIR} < 0;
OffsetPolygon( \@p2legs, $Fighter2->{X} / $GAMEBITS2 - $bgx, $Fighter2->{Y} / $GAMEBITS2 - $bgy );
if ( defined $fr2->{hit} )
{
@p2hit = @{ $fr2->{hit} };
MirrorPolygon( \@p2hit ) if $Fighter2->{DIR} < 0;
OffsetPolygon( \@p2hit, $Fighter2->{X} / $GAMEBITS2 - $bgx, $Fighter2->{Y} / $GAMEBITS2 - $bgy );
}
else
{
undef @p2hit;
}
}
}
diff --git a/data/script/CollectStats.pl b/data/script/CollectStats.pl
index c5d5898..3155fc4 100644
--- a/data/script/CollectStats.pl
+++ b/data/script/CollectStats.pl
@@ -1,35 +1,42 @@
#!/usr/bin/perl -w
+BEGIN {
+ use Cwd;
+ our $directory = cwd;
+}
+
+use lib $directory;
+
require 'FighterStats.pl';
require 'QuickSave.pl';
sub RegisterFighter($)
{
my ($reginfo) = @_;
print "Registering: ", $reginfo->{ID}, "\n";
}
@chars = `ls ../characters/*.pl`;
chomp @chars;
print join (',',@chars), "\n";
foreach $char (@chars) {
require $char;
}
while (($key,$val) = each %FighterStats ) {
delete $val->{STATES};
delete $val->{FRAMES};
delete $val->{STARTCODE};
}
$allstats = store( \%::FighterStats );
$allstats =~ s/^{/\(/s;
$allstats =~ s/}$/\)/s;
open OUTPUT, ">CollectedStats.pl";
print OUTPUT "%::FighterStats = ";
print OUTPUT $allstats;
print OUTPUT ";";
close OUTPUT;
diff --git a/data/script/DataHelper.pl b/data/script/DataHelper.pl
index 1a75a75..c69d130 100644
--- a/data/script/DataHelper.pl
+++ b/data/script/DataHelper.pl
@@ -1,695 +1,701 @@
# DataHelper contains subroutines useful for loading a character's
# frames, and creating his states.
-use strict;
+BEGIN {
+ use Cwd;
+ our $directory = cwd;
+}
+use lib $directory;
+
+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.
LAYER int The "priority" of the graphics. 5: hurt; 10: block; 15: kneeling; 20: normal; 25: attack
=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, $DatName);
# Make sure that Whatever.dat also exists.
$DatName = $DataName;
$DatName =~ s/\.txt$//;
open DATFILE, "../data/characters/$DatName" || die ("Couldn't open ../data/characters/$DatName");
close DATFILE;
open DATAFILE, "../data/characters/$DataName" || die ("Couldn't open ../data/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";
print "Name redefinition: $FrameName!\n" if defined $FrameLookup{ "$FrameName$j" };
$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 GetPolygonCenter($)
{
my ($poly) = @_;
my ($i, $n, $x, $y);
$n = scalar @{$poly};
$x = $y = 0;
for ( $i=0; $i < $n; $i+=2 )
{
$x += $poly->[$i];
$y += $poly->[$i+1];
}
return ( $x*2/$n, $y*2/$n );
}
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 );
# We need to make sure that blocking is the same speed for every character.
# Typical is 5 frames, +- 1 frame
$del = int( 25 / $frames ); # 1/1
$retval = { 'N'=>'Block', 'DEL'=>$del, 'S'=>'+block', };
for ($i = 1; $i <= $frames; ++$i )
{
$retval->{"NEXTST$i"} = "Block " . ($i-1);
$retval->{"CON$i"} = { 'block'=> "Block " . ($i+1) };
$retval->{"BLOCK$i"} = 1 if $i*$del > 10;
}
$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);
}
=comment
JumpStates is for generating the Jump, JumpFW, JumpBW, JumpFly,
JumpStart, JumpKick, JumpPunch states for a state description list.
Parameters:
$frames hash The frame lookup hash.
$con hash Connections during jumping (usually, JumpKick and JumpPunch only)
$framenames hash [optional] If the standard frame names (kneeling,
onknees, kneelingkick, kneelingpunch) are not good, this has should
contain replacement names (e.g. 'kneelingkick' => 'sweep')
=cut
sub JumpStates
{
my ( $frames, $con, $framenames ) = @_;
my ( $kneelingframes, $onkneesframes,
$kickframes, $punchframes ) = (
FindLastFrame( $frames, 'kneeling' ),
FindLastFrame( $frames, 'onknees' ),
FindLastFrame( $frames, 'kneelingkick' ),
FindLastFrame( $frames, 'kneelingpunch' ) );
my ( $jumpheight ) = 120;
my ( $i, $j, $statestotal, $statesdown, $statesknees, $deldown,
$jump, $jumpfw, $jumpbw, $flying, $flyingsequence, $flyingstart, $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 = $jumpheight * 2 / 3 / $::DELMULTIPLIER; # 1/1
}
else
{
$statestotal = $jumpheight * 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'=>$jumpheight, NEXTSTN=>'JumpFly', 'SOUND1'=>'PLAYER_JUMPS', };
$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 };
$flyingstart = { 'N'=>'JumpStart', 'JUMP2'=>$jumpheight, 'PUSHX2'=>9*16, 'DEL1'=>1,
'DEL'=> $deldown, 'S'=>"stand 1,$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, $flyingstart, $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;
unless ( $to )
{
$to = FindLastFrame( $frameLookup, $frameName );
$to += 1 if $frameName eq 'falling';
}
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 ( defined($nextst) and $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;
}
sub CheckStates($$)
{
my ( $fightername, $states ) = @_;
my ( $key,$state, $con );
my ( $seq,$nextst );
while (($key, $state) = each %{$states})
{
die "Bad connection in fighter $fightername to '$state->{NEXTST} from $key!'" unless exists $states->{ $state->{NEXTST} };
next unless $state->{CON};
$con = $state->{CON};
while (($seq, $nextst) = each %{$con})
{
die "Bad connection in fighter $fightername to '$nextst' from $key!" unless exists $states->{$nextst};
}
}
}
return 1;
diff --git a/data/script/Doodad.pl b/data/script/Doodad.pl
index a7bd8cf..c48f8ad 100644
--- a/data/script/Doodad.pl
+++ b/data/script/Doodad.pl
@@ -1,346 +1,352 @@
=comment
Doodad members are:
T int The Doodad's type. 0 means text.
OWNER int The Doodad's owner (0 for player 1, 1 for player 2, other means no owner)
HOSTILE int 1 if the doodad can collision with players and damage them.
LIFETIME int The amount of time before this doodad dies. -1 means infinite.
POS (int,int) The Doodad's logical position.
SIZE (int,int) The Doodad's physical size.
SPEED (int,int) The Doodad's logical speed.
ACCEL (int,int) The Doodad's logical acceleration.
DIR int 1: heading right; -1: heading left (implies flipped state)
GFXOWNER int The Doodad's graphics owner (0 for player 1, 1 for player 2, -1 means global)
FIRSTFRAME int The first frame of the doodad (only meaningful if GFXOWNER is a player).
FRAMES int The number of frames.
SA int Animation speed
F int The Doodad's frame.
TEXT string The text displayed in a text doodad.
INITCODE sub This will run then the doodad is created.
UPDATECODE sub This will be ran instead of MoveDoodad if defined.
Doodad types are:
0 Text
1 Zoli's shot
2 UPi's shot
3 UPi's explosion
4 UPi's familiar
5 Tooth
=cut
+BEGIN {
+ use Cwd;
+ our $directory = cwd;
+}
+
+use lib $directory;
use strict;
require 'Collision.pl';
package Doodad;
%Doodad::DoodadDefinitions = (
'ZoliShot' => {
'T' => 1, 'HOSTILE' => 1, 'LIFETIME' => -1,
'SIZE' => [ 64, 64 ], 'SPEED' => [ 48, -25 ], 'ACCEL' => [ 0, 2 ],
'GFXOWNER' => -1, 'FIRSTFRAME' => 0, 'FRAMES' => 6,
'SA' => 1/10,
},
'Tooth' => {
'T' => 5, 'HOSTILE' => 0, 'LIFETIME' => -1,
'SIZE' => [ 24, 24 ], 'SPEED' => [ 20, -20 ], 'ACCEL' => [ 0, 2 ],
'GFXOWNER' => -1, 'FIRSTFRAME' => 0, 'FRAMES' => 12,
'SA' => 1/4,
'INITCODE' => sub {
my ($self) = @_;
$self->{SA} = $self->{SA} * $self->{DIR};
},
'UPDATECODE' => sub {
my ($self) = @_;
if ( $self->{POS}->[1] > (440 * $::GAMEBITS2) ) {
$self->{SPEED}->[1] = -abs( $self->{SPEED}->[1] / 2 );
$self->{POS}->[1] = 439 * $::GAMEBITS2;
}
MoveDoodad( $self );
},
},
'UPiShot' => {
'T' => 2, 'HOSTILE' => 1, 'LIFETIME' => -1,
'SIZE' => [170, 22], 'SPEED' => [ 48, 0 ], 'ACCEL' => [ 0, 0 ],
'GFXOWNER' => 0, 'FIRSTFRAME' => 340, 'FRAMES' => 5,
'SA' => 1/5,
},
'UPiExplosion' => {
'T' => 3, 'HOSTILE' => 0, 'LIFETIME' => 30,
'SIZE' => [ 58, 80], 'SPEED' => [ 0, 0 ], 'ACCEL' => [ 0, 0 ],
'GFXOWNER' => 0, 'FIRSTFRAME' => 345, 'FRAMES' => 15,
'SA' => 1/2,
},
'UPiFamiliar' => {
'T' => 4, 'HOSTILE' => 0, 'LIFETIME' => -1,
'SIZE' => [ 20, 20], 'SPEED' => [ 0, 0 ], 'ACCEL' => [ 0, 0 ],
'GFXOWNER' => 0, 'FIRSTFRAME' => 360, 'FRAMES' => 3,
'SA' => 1/10,
'ANGLE' => 0,
'UPDATECODE' => sub {
my ($self) = @_;
my ($fighter, $frame, $head, $targetposx, $targetposy);
$fighter = $::Fighters[$self->{OWNER}];
$frame = $fighter->{FRAMES}->[$fighter->{FR}];
$head = $frame->{head};
$self->{ANGLE} += 0.05 * $self->{DIR};
$targetposx = $fighter->{X} + $head->[0] * $::GAMEBITS2 * $fighter->{DIR} + sin( $self->{ANGLE} ) * 300;
$targetposy = $fighter->{Y} + $head->[1] * $::GAMEBITS2 + cos( $self->{ANGLE} ) * 300;
if ( $fighter->{HP} > 0 )
{
$self->{ACCEL}->[0] = ($targetposx - $self->{POS}->[0] ) / 200;
$self->{ACCEL}->[1] = ($targetposy - $self->{POS}->[1] ) / 200;
$self->{SPEED}->[0] += $self->{ACCEL}->[0];
$self->{SPEED}->[1] += $self->{ACCEL}->[1];
$self->{SPEED}->[0] *= 0.95;
$self->{SPEED}->[1] *= 0.95;
}
#$self->{SPEED}->[0] = ($targetposx - $self->{POS}->[0]) / 30;
#$self->{SPEED}->[1] = ($targetposy - $self->{POS}->[1]) / 30;
$self->{POS}->[0] += $self->{SPEED}->[0];
$self->{POS}->[1] += $self->{SPEED}->[1];
$self->{F} += $self->{SA};
$self->{F} -= $self->{FRAMES} if $self->{F} > ($self->{FRAMES} + $self->{FIRSTFRAME});
return 0;
},
},
);
=comment
CreateDoodad creates a new doodad, and appends it to the global doodad list,
@Doodads. The parameters are:
x int The logical horizontal position of the center of the doodad
y int The logical vertical position of the center of the doodad
t string The type of the doodad (doodad types are described above)
dir int 1 or -1
owner int The number of the player who 'owns' the doodad (0 or 1, anything else means no owner)
=cut
sub CreateDoodad
{
my ($x, $y, $t, $dir, $owner) = @_;
my ($doodad, $doodaddef, $w, $h);
$doodaddef = $Doodad::DoodadDefinitions{$t};
if ( ( not defined $doodaddef ) and $t != 0 )
{
print "CreateDoodad: Doodad $doodaddef doesn't exist!\n";
return;
}
if ( defined $doodaddef )
{
$w = $doodaddef->{SIZE}->[0];
$h = $doodaddef->{SIZE}->[1];
$t = $doodaddef->{T};
}
else
{
$w = $h = $t = 0;
$doodaddef = {};
}
$doodad = {
'T' => $t,
'OWNER' => $owner,
'HOSTILE' => 0,
'LIFETIME' => -1,
'POS' => [ $x - $w * $::GAMEBITS2 / 2, $y - $h * $::GAMEBITS2 / 2 ],
'SIZE' => [ $w, $h ],
'SPEED' => [ 0, 0 ],
'ACCEL' => [ 0, 0 ],
'DIR' => $dir,
'GFXOWNER' => $owner,
'FIRSTFRAME'=> 0,
'FRAMES' => 1,
'SA' => 0,
'F' => 0,
'TEXT' => '',
%{$doodaddef},
};
$doodad->{SIZE} = [@{$doodaddef->{SIZE}} ] if defined $doodaddef->{SIZE};
$doodad->{SPEED} = [@{$doodaddef->{SPEED}}] if defined $doodaddef->{SPEED};
$doodad->{ACCEL} = [@{$doodaddef->{ACCEL}}] if defined $doodaddef->{ACCEL};
$doodad->{F} = $doodad->{FIRSTFRAME};
$doodad->{SPEED}->[0] *= $doodad->{DIR};
$doodad->{ACCEL}->[0] *= $doodad->{DIR};
$doodad->{GFXOWNER} = $owner if $doodad->{GFXOWNER} >= 0;
if ( exists $doodad->{INITCODE} )
{
# Call the updatecode.
&{$doodad->{INITCODE}}( $doodad );
}
push @::Doodads, ($doodad);
return $doodad;
}
sub CreateTextDoodad
{
my ($x, $y, $owner, $text) = @_;
my ($self);
$self = CreateDoodad($x, $y, 0, 0, $owner);
$self->{'TEXT'} = $text;
$self->{'GFXOWNER'} = -1;
return $self;
}
sub RewindData
{
my ($self) = @_;
return {
%{$self},
POS => [ @{$self->{POS}} ],
SIZE => [ @{$self->{SIZE}} ],
SPEED => [ @{$self->{SPEED}} ],
ACCEL => [ @{$self->{ACCEL}} ],
}
}
=comment
UpdateDoodad is called once every game tick.
It should return 0 if the doodad is still active.
If UpdateDoodad returns nonzero, it will be removed in this tick and
deleted.
=cut
sub UpdateDoodad
{
my ($doodad) = @_;
if ( exists $doodad->{UPDATECODE} )
{
# Call the updatecode.
return &{$doodad->{UPDATECODE}}( $doodad );
}
else
{
return MoveDoodad( $doodad );
}
}
sub MoveDoodad
{
my ($doodad) = @_;
if ( $doodad->{LIFETIME} >= 0 )
{
$doodad->{LIFETIME} -= 1;
if ( $doodad->{LIFETIME} < 0 )
{
# Doodad dies.
return 1;
}
}
$doodad->{SPEED}->[0] += $doodad->{ACCEL}->[0];
$doodad->{SPEED}->[1] += $doodad->{ACCEL}->[1];
$doodad->{POS} ->[0] += $doodad->{SPEED}->[0];
$doodad->{POS} ->[1] += $doodad->{SPEED}->[1];
$doodad->{F} += $doodad->{SA};
$doodad->{F} -= $doodad->{FRAMES} if $doodad->{F} > ($doodad->{FRAMES} + $doodad->{FIRSTFRAME});
$doodad->{F} += $doodad->{FRAMES} if $doodad->{F} < $doodad->{FIRSTFRAME};
if ( $doodad->{POS}->[0] > $::BGWIDTH2 ) { return 1; }
if ( $doodad->{POS}->[0] < $doodad->{SIZE}->[0] * $::GAMEBITS2 ) { return 1; }
if ( $doodad->{POS}->[1] > $::SCRHEIGHT2 ) { return 1; }
if ( $doodad->{POS}->[1] < $doodad->{SIZE}->[1] * $::GAMEBITS2 ) { return 1; }
if ( $doodad->{HOSTILE} )
{
CheckDoodadHit($doodad);
}
# print "Doodad: POS=", join(',', @{$doodad->{POS}}),
# "; SPEED=", join(',', @{$doodad->{SPEED}}),
# "; ACCEL=", join(',', @{$doodad->{ACCEL}}), "\n";
return 0;
}
sub CheckDoodadHit($)
{
my ( $self ) = @_;
my ( @poly, $x, $y, $w, $h, $i, $fighter );
$x = $self->{POS}->[0] / $::GAMEBITS2;
$y = $self->{POS}->[1] / $::GAMEBITS2;
$w = $self->{SIZE}->[0];
$h = $self->{SIZE}->[1];
for ( $i=0; $i<$::NUMPLAYERS; ++$i )
{
next if $i == $self->{OWNER};
$fighter = $::Fighters[$i];
@poly = (
$x, $y,
$x+$w, $y,
$x+$w, $y+$h,
$x, $y+$h );
if ( $fighter->IsHitAt( \@poly ) )
{
DoHitPlayer($self, $fighter);
}
}
}
sub DoHitPlayer($$)
{
my ($self,$player) = @_;
$self->{HOSTILE} = 0;
$player->HitEvent( $::Fighters[$self->{OWNER}], 'Hit', $self->{T} );
}
=cut
return 1;
diff --git a/data/script/Fighter.pl b/data/script/Fighter.pl
index 757c699..1448f88 100644
--- a/data/script/Fighter.pl
+++ b/data/script/Fighter.pl
@@ -1,930 +1,938 @@
+
+BEGIN {
+ use Cwd;
+ our $directory = cwd;
+}
+
+use lib $directory;
+
require 'Collision.pl';
require 'DataHelper.pl';
require 'Damage.pl';
package Fighter;
use strict;
=comment
Fighter's members are:
ID int The ID of the fighter
STATS hash Reference to the fighter's stats (includes GENDER)
NAME string The name of the character, e.g. "Ulmar".
FRAMES array The character's frame description.
STATES hash The character's state description.
OK bool Is the fighter good to go?
TEAMSIZE int The number of fighters left in this figther's team, including this one. 0 when the last one dies.
NUMBER int Player number (either 0 or 1)
X int The fighter's current anchor, horizontal coordinate.
Y int The fighter's current anchor, vertical coordinate.
ST string The name of the fighter's current state.
FR int The number of the fighter's current frame (same as STATES->{ST}->{F}).
DEL int The amount of time before the character moves to the next state.
NEXTST string The name of the next state after this one (calculated by Advance and Event, user by Update).
DIR int -1: the character is facing left; 1: if the character is facing right.
PUSHY int The character's vertical momentum, from jumping/hits.
PUSHX int The character's horizontal momentum, from hits.
HP int Hit points, from 100 down to 0.
IDLE int The amount of game time since the player is ready.
CHECKEHIT int 1 if the hit needs to be checked soon.
DELIVERED int 1 if a hit was delivered in this state.
COMBO int The number of consecutive hits delivered to this fighter.
COMBOHP int The amount of HP delivered in the last combo.
OTHER Fighter A reference to the other Fighter
LANDINGPENALTY int This is added to DEL when the character lands (used to penaltize blocked jumpkicks). Becomes DELPENALTY upon landing.
DELPENALTY int This is added to DEL in the next state.
BOUNDSCHECK bool Should horizontal bounds checking be done for this fighter (for team mode when new fighter enters)'
new
Reset
Advance
CheckHit
IsHitAt
Event
Update
Needs to be subclassed: Reset, NextState
=cut
sub new {
my ($class) = @_;
my $self = {
'NUMBER'=> 0,
'ST' => 'Start',
'FR' => 0,
'DEL' => 0,
'DIR' => 1,
'PUSHY' => 0,
'PUSHX' => 0,
};
bless $self, $class;
return $self;
}
sub RewindData {
my ($self) = @_;
return $self;
}
sub Reset {
my ($self, $fighterenum) = @_;
die "Insufficient parameters." unless defined $self;
$fighterenum = $self->{ID} unless defined $fighterenum;
my ($number, $stats);
$number = $self->{NUMBER};
$stats = ::GetFighterStats($fighterenum);
die "Couldn't load stats of fighter $fighterenum\n" unless defined $stats;
unless (defined $stats->{STATES})
{
print "ERROR: The fighter $fighterenum is not yet usable.\n";
$self->{OK} = 0;
return;
}
print STDERR "Resetting fighter $number to character $fighterenum\n";
$self->{ID} = $fighterenum;
$self->{STATS} = $stats;
$self->{NAME} = $stats->{NAME};
$self->{FRAMES} = $stats->{FRAMES};
$self->{STATES} = $stats->{STATES};
$self->{X} = (( $number ? 540 : 100 ) << $::GAMEBITS) + $::BgPosition;
$self->{X} = (( $number ? 620 : 180 ) << $::GAMEBITS) + $::BgPosition if $::WIDE;
$self->{Y} = $::GROUND2;
$self->{ST} = 'Start';
$self->{FR} = $self->GetCurrentState()->{F};
$self->{DEL} = 0;
$self->{DIR} = ($number ? -1 : 1);
$self->{PUSHY} = 0;
$self->{PUSHX} = 0;
$self->{HP} = 100;
$self->{IDLE} = 0;
$self->{CHECKHIT} = 0;
$self->{DELIVERED} = 0;
$self->{COMBO} = 0;
$self->{COMBOHP}= 0;
$self->{LANDINGPENALTY} = 0;
$self->{DELPENALTY} = 0;
$self->{BOUNDSCHECK} = 1;
$self->{OK} = 1;
&{$self->{STATS}->{STARTCODE}}($self);
}
=comment
Returns a "random" number that is deterministic in the sense that it is
always the same, given the current state of the character and the current
game time.
=cut
sub QuasiRandom($$)
{
my ( $self, $randmax ) = @_;
$randmax = 1 unless $randmax;
return int( $self->{HP} * 543857
+ $self->{NUMBER} * 834973
+ $self->{DEL} * 4358397
+ $self->{IDLE} * 92385029
+ $::gametick * 23095839
+ 5304981 ) % $randmax;
}
=comment
Advance should be called once every game tick. It advances the fighter,
preparing his variables for drawing him in the next tick. Advance also
processes the input if it is appropriate to do so in the current state.
Advance prepares the NEXTST attribute of the player. It does not modify
the current ST yet, because that may depend on hits, event, etc. Advance
should be followed by CheckHit and Event calls, and finally Update.
=cut
sub Advance {
my ($self, $in) = @_;
# print STDERR "$::gametick\t$self->{X},$self->{HP}\t$self->{NUMBER}\t$self->{DEL}\t$self->{IDLE}\n";
my ($stname, # The name of the current state.
$st, # The descriptor of the current state.
$move, # The move associated with the current state.
$i, $j, # temp var.
@body ); # collision detection
$self->{NEXTST} = '';
$stname = $self->{ST};
return if ( $stname eq 'Dead' );
$st = $self->{'STATES'}->{$stname};
# 1. DECREMENT 'DEL' PROPERTY
$self->{'DEL'}--;
if ( $st->{SITU} eq 'Ready' )
{
$self->{IDLE} = $self->{IDLE} + 1;
}
else
{
$self->{IDLE} = 0;
}
# 2. UPDATE THE HORIZONTAL POSITION
# 2.1. 'MOVE' PLAYER IF NECESSARY
if ( defined $st->{'MOVE'} )
{
$move = $st->{'MOVE'}*$self->{'DIR'} * 6;
$self->{'X'} += $move;
}
# 2.2. 'PUSH' PLAYER IF NECESSARY
if ( $self->{PUSHX} )
{
$i = ($self->{PUSHX}+7) / 8; # 1/1; syntax highlight..
$self->{X} += $i;
$self->{PUSHX} -= $i if $self->{PUSHY} == 0;
}
# 2.3. MAKE SURE THE TWO FIGHTERS DON'T "WALK" INTO EACH OTHER
if ( $::BgScrollEnabled and $self->{Y} >= $::GROUND2 )
{
my ( $other, $centerX, $centerOtherX, $pushDir );
$centerX = $self->GetCenterX;
for ($i=0; $i<$::NUMPLAYERS; ++$i)
{
$other = $::Fighters[$i];
next if ( $other->{Y} < $::GROUND2 ) or ( $other->{ST} eq 'Dead' );
$centerOtherX = $other->GetCenterX;
if ( abs($centerX - $centerOtherX) < 60 )
{
$pushDir = ($centerX > $centerOtherX) ? 1 : -1;
$self->{X} += 10 * $pushDir;
$other->{X} -= 10 * $pushDir;
}
}
}
# 2.4. HORIZONTAL BOUNDS CHECKING
if ( $self->{BOUNDSCHECK} )
{
$self->{X} = $::BgPosition + $::MOVEMARGIN2
if $self->{X} < $::BgPosition + $::MOVEMARGIN2;
$self->{X} = $::BgPosition + $::SCRWIDTH2 - $::MOVEMARGIN2
if $self->{X} > $::BgPosition + $::SCRWIDTH2 - $::MOVEMARGIN2;
}
# 3. FLYING OR FALLING
if ( $self->{Y} < $::GROUND2 )
{
$self->{PUSHY} += 3;
$self->{Y} += $self->{PUSHY};
if ($self->{Y} >= $::GROUND2)
{
# LANDING
$self->{DELPENALTY} = $self->{LANDINGPENALTY};
$self->{LANDINGPENALTY} = 0;
$self->{BOUNDSCHECK} = 1;
if ( $st->{SITU} eq 'Falling')
{
::AddEarthquake( $self->{PUSHY} / 20 ); # 1/1
push @::Sounds, ('PLAYER_FALLS') if $self->{PUSHY} > 40;
# print "PUSHY = ", $self->{PUSHY}, "; ";
if ( $self->{PUSHY} > 30 )
{
# Bouncing after a fall
$self->{PUSHY} = - $self->{PUSHY} / 2; # 1/1
$self->{Y} = $::GROUND2 - 1;
}
else
{
# Getting up after a fall.
$self->{PUSHY} = 0;
$self->{Y} = $::GROUND2;
$self->{DEL} = 0;
if ( $self->{HP} <= 0 ) {
$self->{NEXTST} = 'Dead';
}
else {
$self->{NEXTST} = 'Getup';
}
}
return;
}
else
{
push @::Sounds, ('PLAYER_LANDS');
$self->{PUSHY} = 0;
$self->{Y} = $::GROUND2;
if ( substr($self->{ST},0,4) eq 'Jump' )
{
# Landing after a jump
$self->{DEL} = 0;
$self->{NEXTST} = 'Stand';
}
else
{
print "Landed; state: ", $self->{ST}, "\n";
}
return;
}
}
}
return if $self->{'DEL'} > 0;
# 4. DELAYING OVER. MAKE END-OF-STATE CHANGES.
# 4.1. 'DELTAX' displacement if so specified
if ($st->{DELTAX})
{
$self->{X} += $st->{DELTAX} * $self->{DIR} * $::GAMEBITS2;
}
# 4.2. 'TURN' if so specified
if ($st->{TURN})
{
$self->{DIR} = - $self->{DIR};
# Swap input keys..
$i = $in->{Keys}->[2];
$in->{Keys}->[2] = $in->{Keys}->[3];
$in->{Keys}->[3] = $i;
}
# 5. CALCULATE THE NEXT STATE
my ($nextst, $con, $input, $mod, $action, $dist);
$nextst = $st->{NEXTST};
$con = $st->{CON};
undef $con if $::ko;
if ( defined $con )
{
$dist = ($self->{OTHER}->{X} - $self->{X}) * $self->{DIR};
$self->ComboEnds();
# 5.1. The current state has connections!
($input,$mod) = $in->GetAction();
$mod = '' unless defined $mod;
$action = $input;
# Try to find the best matching connection.
for ( $i = length($mod); $i>=0; --$i )
{
$action = $input . substr( $mod, 0, $i );
last if defined $con->{$action};
}
if ( defined $con->{$action} )
{
# print "dist = $dist. ";
if ( ($action eq 'hpunch' or $action eq 'lpunch') and ($dist>=0) and ($dist<800) )
{
$action = 'hpunchF' if defined $con->{'hpunchF'};
}
elsif ( ($action eq 'hkick' or $action eq 'lkick') and ($dist>=0) and ($dist<900) )
{
$action = 'lkickF' if defined $con->{'lkickF'};
}
$nextst = $con->{$action};
$in->ActionAccepted;
if ( $nextst eq 'Back' and $::NUMPLAYERS != 2 ) {
$nextst = 'Turn';
}
}
}
$self->{'NEXTST'} = $nextst;
}
=comment
Checks for hits on all other fighters.
Returns a list of ($self,$fighter,$hit) triplets.
=cut
sub CheckHit($)
{
my ($self) = @_;
return () unless $self->{CHECKHIT};
$self->{CHECKHIT} = 0;
my ($st, # A reference to the next state.
$nextst, # The name of the next state.
@hit, # The hit array
@hit2,
$i, $j,
@retval,
$other,
);
$st = $self->{STATES}->{$self->{ST}};
return @retval unless $st->{HIT};
return @retval unless defined $self->{FRAMES}->[$st->{F}]->{hit};
@hit = @{$self->{FRAMES}->[$st->{F}]->{hit}};
if ( $self->{DIR}<0 )
{
::MirrorPolygon( \@hit );
}
::OffsetPolygon( \@hit,
$self->{X} / $::GAMEBITS2,
$self->{Y} / $::GAMEBITS2 );
for ( $j=0; $j<$::NUMPLAYERS; ++$j )
{
$other = $::Fighters[$j];
next if $other == $self;
@hit2 = @hit;
$i = $other->IsHitAt( \@hit2 );
if ( $i ) {
$self->{DELIVERED} = 1;
push @retval, ([$self, $other, $i]);
}
}
if ( scalar(@retval) == 0 )
{
# Make the 'Woosh' sound - maybe.
$nextst = $st->{NEXTST};
print "NEXTST = $nextst";
$nextst = $self->{STATES}->{$nextst};
print "NEXTST->HIT = ", $nextst->{HIT}, "\n";
push @::Sounds, ('ATTACK_MISSES') unless $nextst->{HIT} and defined $self->{FRAMES}->[$nextst->{F}]->{hit};
}
return @retval;
}
sub IsHitAt
{
my ($self, $poly) = @_;
my ($frame, @a);
$frame = $self->{FRAMES}->[$self->{FR}];
::OffsetPolygon( $poly, -$self->{X} / $::GAMEBITS2, -$self->{Y} / $::GAMEBITS2 );
if ( $self->{DIR}<0 )
{
::MirrorPolygon( $poly );
}
#print "IsHitAt (", join(',',@{$poly}),")\n (",
# join(',',@{$frame->{head}}),")\n (",
# join(',',@{$frame->{body}}),")\n (",
# join(',',@{$frame->{legs}}),")\n";
return 1 if ::Collisions( $poly, $frame->{head} );
return 2 if ::Collisions( $poly, $frame->{body} );
return 3 if ::Collisions( $poly, $frame->{legs} );
return 0;
}
=comment
Event($self, $event, $eventpar): Handles the following events:
'Won', 'Hurt', 'Thread', 'Fun', 'Turn'
=cut
sub Event($$$)
{
my ($self, $event, $eventpar) = @_;
my ($st);
$st = $self->GetCurrentState();
if ( ($st->{SITU} eq 'Ready' or $st->{SITU} eq 'Stand') and $event eq 'Won' )
{
$self->{NEXTST} = 'Won' unless substr($self->{ST}, 0, 3) eq 'Won' ;
return;
}
if ( $st->{SITU} eq 'Ready' and $self->{NEXTST} eq '' )
{
$self->{IDLE} = 0;
if ( $event =~ /Hurt|Threat|Fun|Turn/ )
{
if ( $event eq 'Fun' and defined $self->{STATES}->{'Funb'} and $self->QuasiRandom(2)==1 )
{
$event = 'Funb';
}
$self->{NEXTST} = $event;
}
}
}
=comment
Event($self, $event, $eventpar): Handles the following events:
'Highhit', 'Uppercut', 'Hit', 'Groinhit', 'Leghit', 'Fall'
Parameters:
$other Fighter The fighter who hit me.
$event string The name of the event (any of the above events)
$eventpar int/string 1: head hit; 2: body hit; 3: leg hit; Maxcombo: I did a max combo (=fall with 0 damage)
=cut
sub HitEvent($$$$)
{
my ($self, $other, $event, $eventpar) = @_;
my ($st, $blocked, $damage);
$st = $self->GetCurrentState();
# Do events: Highhit, Uppercut, Hit, Groinhit, Leghit, Fall
$eventpar = '' unless defined $eventpar; # Supress useless warning
if ( $eventpar eq 'Maxcombo' ) { $damage = 0; }
else { $damage = ::GetDamage( $other->{NAME}, $other->{ST} ); }
$blocked = $st->{BLOCK};
$blocked = 0 if ( $self->IsBackTurnedTo($other) );
print "Event '$event', '$eventpar'\n";
print "Blocked.\n" if ( $blocked );
# Hit point adjustment here.
$self->{HP} -= $blocked ? $damage >> 3 : $damage;
# Turn if we must.
$self->{DIR} = ( $self->{X} > $other->{X} ) ? -1 : 1;
# Handle the unfortunate event of the player "dying".
if ( $self->{HP} <= 0 )
{
push @::Sounds, ('PLAYER_KO');
$self->{NEXTST} = 'Falling';
$self->{PUSHX} = -20 * 3 * $self->{DIR};
$self->{PUSHY} = -80;
$self->{Y} -= 10;
$self->{COMBO} += 1;
$self->{COMBOHP} += $damage;
$self->ComboEnds();
$::ko = 1 if $self->{TEAMSIZE} <= 1 and $::ActiveTeams <= 2;
return;
}
# Handle blocked attacks.
if ( $blocked )
{
push @::Sounds, ('ATTACK_BLOCKED');
$self->HitPush( $other, - $damage * 20 * $self->{DIR} );
$other->{DEL} += 20 * $::DELMULTIPLIER;
if ( $other->{Y} < $::GROUND2 )
{
$other->{PUSHY} = -20 if $other->{PUSHY} > 0;
$other->{PUSHX} *= 0.2;
$other->{LANDINGPENALTY} = 30 * $::DELMULTIPLIER;
}
# $self->{PUSHX} = - $damage * 5 * $self->{DIR};
return;
}
# Handle the rest of the events.
if ( $event eq 'Uppercut' )
{
push @::Sounds, ('UPPERCUT_HITS');
::AddEarthquake( 20 );
}
elsif ($event eq 'Groinhit')
{
push @::Sounds, ('GROINKICK_HITS');
}
else
{
push @::Sounds, ('ATTACK_HITS');
}
$self->{COMBO} += 1;
$self->{COMBOHP} += $damage;
$damage *= $self->{COMBO}; # Only for the purpose of pushing
if ( $self->{COMBO} >= $::MAXCOMBO )
{
$self->ComboEnds();
$event = 'Uppercut';
$other->HitEvent( 'Fall', 'Maxcombo' );
}
if ( $st->{SITU} eq 'Crouch' )
{
if ( $event eq 'Uppercut' or $event eq 'Fall' )
{
$self->{NEXTST} = 'Falling';
$self->{PUSHX} = -48 * 3 * $self->{DIR};
$self->{PUSHY} = -100;
$self->{Y} -= 10;
}
else
{
if ($eventpar == 1) {
$self->{NEXTST} = 'KneelingPunched';
} else {
$self->{NEXTST} = 'KneelingKicked';
}
$self->HitPush( $other, - $damage * 20 * $self->{DIR} );
# $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
return;
}
if ( $st->{SITU} eq 'Falling' )
{
$self->{PUSHY} -= 50;
$self->{PUSHY} = -80 if $self->{PUSHY} > -80;
$self->{Y} -= 10;
return;
}
if ( $self->{Y} < $::GROUND2 )
{
$self->{NEXTST} = 'Falling';
$self->{PUSHY} -= 50;
$self->{PUSHX} = -48 * 3 * $self->{DIR};
return;
}
if ( $event eq 'Highhit' )
{
my ( $doodadx, $doodady );
($doodadx, $doodady) = ::GetPolygonCenter( $self->{FRAMES}->[$self->{FR}]->{head} );
# my ( $doodadx, $doodady ) = @{$self->{FRAMES}->[$self->{FR}]->{head}};
Doodad::CreateDoodad( $self->{X} + $doodadx * $::GAMEBITS2 * $self->{DIR},
$self->{Y} + $doodady * $::GAMEBITS2,
'Tooth',
- $self->{DIR},
$self->{NUMBER} );
$self->{NEXTST} = 'HighPunched';
$self->HitPush( $other, - $damage * 20 * $self->{DIR} );
# $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
elsif ( $event eq 'Hit' )
{
if ($eventpar == 1) {
$self->{NEXTST} = 'HighPunched';
} elsif ($eventpar == 2) {
$self->{NEXTST} = 'LowPunched';
} else {
$self->{NEXTST} = 'Swept';
}
$self->HitPush( $other, - $damage * 20 * $self->{DIR} );
# $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
elsif ( $event eq 'Groinhit' )
{
$self->{NEXTST} = 'GroinKicked';
$self->HitPush( $other, - $damage * 20 * $self->{DIR} );
# $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
elsif ( $event eq 'Leghit' )
{
$self->{NEXTST} = 'Swept';
$self->HitPush( $other, - $damage * 20 * $self->{DIR} );
# $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
elsif ( $event eq 'Uppercut' or $event eq 'Fall' )
{
$self->{NEXTST} = 'Falling';
$self->{PUSHX} = -48 * 3 * $self->{DIR};
$self->{PUSHY} = -100;
$self->{Y} -= 10;
}
else
{
die "Unknown event: $event, $eventpar";
}
}
sub GetCurrentState
{
my ($self, $statename) = @_;
my ($stateref);
die unless defined $self;
$statename = $self->{ST} unless defined $statename;
$stateref = $self->{STATES}->{$statename};
return $stateref;
}
sub ComboEnds
{
my ($self) = @_;
my ($combo, $ismaxcombo);
$combo = $self->{COMBO};
$ismaxcombo = $combo >= $::MAXCOMBO;
return unless $combo;
if ( $self->{COMBO} > 1 )
{
my ( $head, $doodad, $x, $y, $combotext );
$combotext = $ismaxcombo ? ::Translate("MAX COMBO!!!") : sprintf( ::Translate('%d-hit combo!'), $self->{COMBO} );
$head = $self->{FRAMES}->[$self->{FR}]->{head};
$x = $self->{X} + $head->[0] * $::GAMEBITS2 * $self->{DIR};
$y = $self->{Y} + $head->[1] * $::GAMEBITS2;
$doodad = Doodad::CreateTextDoodad( $x, $y - 30 * $::GAMEBITS2,
$self->{NUMBER},
$combotext );
$doodad->{LIFETIME} = $ismaxcombo ? 120 : 80;
$doodad->{SPEED} = [-3,-3];
$doodad = Doodad::CreateTextDoodad( $x, $y - 10 * $::GAMEBITS2,
$self->{NUMBER},
sprintf( ::Translate('%d%% damage'), int($self->{COMBOHP}*$::HitPointScale/10) ) );
$doodad->{LIFETIME} = 80;
$doodad->{SPEED} = [+3,-3];
print $self->{COMBO}, "-hit combo for ", $self->{COMBOHP}, " damage.\n";
push @::Sounds, ( $ismaxcombo ? 'MAX_COMBO' : 'COMBO');
}
$self->{COMBO} = 0;
$self->{COMBOHP} = 0;
}
=comment
Update moves the fighter to his next state, if there is a NEXTST set.
NEXTST should be calculated before calling this method. No further
branching or CON checking is done.
If the next state has JUMP, the character is set aflying.
=cut
sub Update
{
my ($self) = @_;
my ($nextst, # The name of the next state
$st); # The descriptor of the next state.
# Is there a next state defined?
$nextst = $self->{'NEXTST'};
# If there isn't, no updating is necessary.
return unless $nextst;
# ADMINISTER END OF THE STATE MACHINE (WON2 OR DEAD2)
if ( $nextst eq 'Won2' or $nextst eq 'Dead' ) {
$self->{DELPENALTY} = 1000000; # Something awfully large
$self->{HP} = -10000 if $nextst eq 'Dead';
--$::ActiveFighters;
--$::ActiveTeams if $self->{TEAMSIZE}<=1 or $nextst eq 'Won2';
}
# ADMINISTER THE MOVING TO THE NEXT STATE
$st = $self->GetCurrentState( $nextst );
$self->{'ST'} = $nextst;
$self->{'FR'} = $st->{'F'};
die "ERROR IN STATE $nextst" unless defined $st->{DEL};
$self->{'DEL'} = $st->{'DEL'} * $::DELMULTIPLIER;
$self-> {'DEL'} += $self->{'DELPENALTY'};
$self->{'DELPENALTY'} = 0;
# HANDLE THE JUMP and PUSH ATTRIBUTE
if ( defined ($st->{'JUMP'}) )
{
$self->{'PUSHY'} = -($st->{'JUMP'});
$self->{'Y'} += $self->{'PUSHY'};
}
if ( defined ($st->{'PUSHX'}) )
{
$self->{'PUSHX'} += $st->{'PUSHX'} * $self->{DIR};
}
# HANDLE THE HIT ATTRIBUTE
if ( defined ($st->{HIT}) )
{
$self->{CHECKHIT} = 1 unless $self->{DELIVERED};
}
else
{
$self->{DELIVERED} = 0;
}
# HANDLE THE SOUND ATTRIBUTE
if ( defined $st->{SOUND} )
{
push @::Sounds, ($st->{SOUND});
}
# HANDLE THE CODE ATTRIBUTE
if ( defined ($st->{CODE}) )
{
eval ($st->{CODE}); print $@ if $@;
}
# HANDLE DOODADS
if ( defined ($st->{DOODAD}) )
{
# Create a doodad (probably a shot)
my ($frame, $hit, $doodad, $doodadname);
$frame = $self->{FRAMES}->[$self->{FR}];
$hit = $frame->{hit};
if ( defined $hit )
{
$doodadname = $st->{DOODAD};
$doodad = Doodad::CreateDoodad(
$self->{X} + $hit->[0] * $::GAMEBITS2 * $self->{DIR},
$self->{Y} + $hit->[1] * $::GAMEBITS2,
$doodadname,
$self->{DIR},
$self->{NUMBER} );
}
}
}
=comment
Pushes the fighter back due to being hit. If the fighter is cornered,
the other fighter will be pushed back instead.
=cut
sub HitPush($$$)
{
my ($self, $other, $pushforce) = @_;
if ( $self->IsCornered )
{
$other->{PUSHX} -= $pushforce;
}
else
{
$self->{PUSHX} += $pushforce;
}
}
=comment
Returns the characters 'centerline' in physical coordinates.
=cut
sub GetCenterX
{
my ($self) = @_;
my ($body, $x);
$body = $self->{FRAMES}->[$self->{FR}]->{body};
$x = $body->[0] + $body->[2] + $body->[4] + $body->[6];
return $self->{X} / $::GAMEBITS2 + $x / 4 * $self->{DIR};
}
=comment
Is my back turned to my opponent? Returns true if it is.
=cut
sub IsBackTurned
{
my ($self) = @_;
return ( ($self->{X} - $self->{OTHER}->{X}) * ($self->{DIR}) > 0 );
}
=comment
Is my back turned to my opponent? Returns true if it is.
=cut
sub IsBackTurnedTo($)
{
my ($self, $other) = @_;
return ( ($self->{X} - $other->{X}) * ($self->{DIR}) > 0 );
}
=comment
Returns true if the character is at either end of the arena.
=cut
sub IsCornered
{
my ($self) = @_;
return (($self->{X} <= $::MOVEMARGIN2 + 16)
or ($self->{X} >= $::BGWIDTH2 - $::MOVEMARGIN2 - 16));
}
return 1;
diff --git a/data/script/Rewind.pl b/data/script/Rewind.pl
index ef60936..be12733 100644
--- a/data/script/Rewind.pl
+++ b/data/script/Rewind.pl
@@ -1,86 +1,84 @@
-
-
use strict;
$::RewindInterwal = 10; # Save every 10th game tick
$::RewindMax = 200; # Must be a multiple of $RewindInterwal
%::RewindData = ();
sub ResetRewind()
{
%::RewindData = ();
}
sub MakeRewindData()
{
my ( $doodadrewind, $doodad, $rewinddata );
$doodadrewind = [];
foreach $doodad (@::Doodads) {
push @{$doodadrewind}, ( $doodad->RewindData() );
}
$rewinddata = {
gametick => $::gametick,
fighter1 => $::Fighter1->RewindData(),
fighter2 => $::Fighter2->RewindData(),
doodads => $doodadrewind,
bgx => $::bgx,
bgy => $::bgy,
bgspeed => $::BgSpeed,
bgposition => $::BgPosition,
over => $::over,
ko => $::ko,
quakeamplitude => $::QuakeAmplitude,
quakeoffset => $::QuakeOffset,
quakex => $::QuakeX,
quakey => $::QuakeY,
input1 => $::Input1->RewindData(),
input2 => $::Input2->RewindData(),
};
$::RewindData{$::gametick} = $rewinddata;
delete $::RewindData{$::gametick - $::RewindMax};
}
sub RewindTo($)
{
my ($rewindto) = @_;
my ($rewinddata);
$rewindto = (int($rewindto / $::RewindInterwal) * $::RewindInterwal); #/
$rewinddata = $::RewindData{$rewindto};
return unless defined $rewinddata;
$::gametick = $rewinddata->{gametick};
$::Fighter1 = $rewinddata->{fighter1};
$::Fighter2 = $rewinddata->{fighter2};
@::Doodads = @{$rewinddata->{doodads}};
$::bgx = $rewinddata->{bgx};
$::bgy = $rewinddata->{bgy};
$::BgSpeed = $rewinddata->{bgspeed};
$::BgPosition = $rewinddata->{bgposition};
$::over = $rewinddata->{over};
$::ko = $rewinddata->{ko};
$::QuakeAmplitude = $rewinddata->{quakeamplitude};
$::QuakeOffset = $rewinddata->{quakeoffset};
$::QuakeX = $rewinddata->{quakex};
$::QuakeY = $rewinddata->{quakey};
$::Input1 = $rewinddata->{input1};
$::Input2 = $rewinddata->{input2};
}
1;
diff --git a/data/script/State.pl b/data/script/State.pl
index b34594b..5b5d6cb 100644
--- a/data/script/State.pl
+++ b/data/script/State.pl
@@ -1,25 +1,26 @@
+
use strict;
sub ParseConfig($)
{
my ($filename) = @_;
my ($escaped1, $escaped2);
open CONFFILE, "$filename" or return;
while (<CONFFILE>)
{
if ($_ =~ /(\w+)\s*=\s*(.*\S)\s*/)
{
$escaped1 = $1;
$escaped1 =~ s/'/\\'/g;
$escaped2 = $2;
$escaped2 =~ s/'/\\'/g;
eval( "\$::$escaped1 = '$escaped2';" );
}
}
close CONFFILE;
}
return 1;
diff --git a/data/script/Translate.pl b/data/script/Translate.pl
index 8c37e68..0bdeef7 100644
--- a/data/script/Translate.pl
+++ b/data/script/Translate.pl
@@ -1,1013 +1,1014 @@
+
use strict;
use bytes;
=comment
Translate.pl attempts to load all of the translations into one file. A
translation is simply a hash from English to the other language.
=cut
$::English = { 'LanguageCode' => 0,
# In-game text
'MAX COMBO!!!' => undef,
'%d-hit combo!' => undef, # e.g. 3-hit combo!
'%d%% damage' => undef, # e.g. 30% damage
'Round %d' => undef,
'REW' => undef,
'REPLAY' => undef,
'DEMO' => undef,
'Press F1 to skip...'=> undef,
'HURRY UP!' => undef,
'TIME IS UP!' => undef,
'Final Judgement' => undef,
'Continue?' => undef,
'SPLAT!' => undef,
'Choose A Fighter Dammit' => undef,
# Menu items
"Main Menu" => undef,
"~SINGLE PLAYER GAME"=> undef,
"~MULTI PLAYER GAME"=> undef,
"~SURRENDER GAME" => undef,
"~OPTIONS" => undef,
"~INFO" => undef,
"QUIT" => undef,
"~OK" => undef,
"~LANGUAGE: " => undef,
"Options" => undef,
"~FULLSCREEN ON" => undef,
"~FULLSCREEN OFF" => undef,
"GAME SPEED: " => undef,
"GAME TIME: " => undef,
"STAMINA: " => undef,
"~SOUND" => undef,
"~RIGHT PLAYER KEYS"=> undef,
"~LEFT PLAYER KEYS" => undef,
"Sound" => undef,
"CHANNELS: " => undef, # Mono / Stereo
"SOUND QUALITY: " => undef, # KHz of playback rate
"SOUND FIDELITY: " => undef, # 8 bit or 16 bit
"MUSIC VOLUME: " => undef, # OFF or numeric
"EFFECTS VOLUME: " => undef, # OFF or numeric
# Menu options
"BABY" => undef,
"VERY LOW" => undef,
"LOW" => undef,
"NORMAL" => undef,
"HIGH" => undef,
"VERY HIGH" => undef,
"NEAR IMMORTAL" => undef,
"SNAIL RACE" => undef,
"SLOW" => undef,
"NORMAL" => undef,
"TURBO" => undef,
"KUNG-FU MOVIE" => undef,
# Sound / Channels
"MONO" => undef,
"STEREO" => undef,
# Sound / Mixing rate settings
"LOW" => undef,
"MEDIUM" => undef,
"HIGH" => undef,
# Sound volume
"OFF" => undef,
# Key configuration
'%s player-\'%s\'?' => undef, # The first %s becomes Left or Right. The second %s is up/down/high punch/...
'Left' => undef,
'Right' => undef,
"up", => undef,
"down", => undef,
"left", => undef,
"right", => undef,
"block", => undef,
"low punch", => undef,
"high punch", => undef,
"low kick", => undef,
"high kick" => undef,
'Thanks!' => undef,
# Demo screens,
'Fighter Stats' => undef,
'Unfortunately this fighter is not yet playable.' => undef,
'Name: ' => undef,
'Team: ' => undef,
'Style: ' => undef,
'Age: ' => undef,
'Weight: ' => undef,
'Height: ' => undef,
'Shoe size: ' => undef,
"Credits" => undef,
"CreditsText1" =>
"OPENMORTAL CREDITS
-- THE OPENMORTAL TEAM ARE --
CODING - UPi
MUSIC - Purple Motion
MUSIC - XTD / Mystic
GRAPHICS - UPi
German translation - ??
French translation - Vampyre
Spanish translation - EdsipeR
Portuguese translation - Vinicius Fortuna
-- CAST --
Boxer - Zoli
Cumi - As himself
Descant - As himself
Fureszes Orult - Ambrus
Grizli - As himself
Kinga - As herself
Macy - As herself
Misi - As himself
Rising-san - Surba
Sirpi - As himself
Taka Ito - Bence
Tokeletlen Katona - Dani
Watasiwa Baka Janajo - Ulmar
Black Dark Evil Mage - UPi
-- HOSTING --
sourceforge.net
apocalypse.rulez.org
freshmeat.net
OpenMortal is Copyright 2003 of the OpenMortal Team
Distributed under the GNU General Public Licence Version 2\n\n",
'CreditsText2' =>
'Thanks to Midway for not harrassing us with legal stuff so far, even though '.
'we must surely violate at least 50 of their patents, international copyrights and registered trademarks.
OpenMortal needs your help! If you can contribute music, graphics, improved code, '.
'additional characters, cash, beer, pizza or any other consumable, please mail us '.
'at upi@apocalypse.rulez.org! The same address is currently accepting comments and '.
"fanmail too (hint, hint!).\n\n",
'CreditsText3' =>
"Be sure to check out other stuff from
Apocalypse Production
and
Degec Entertainment\n\n",
'Story1Text' =>
"We, the Gods of the Evil Killer Black Antipathic Dim (witted) Fire Mages no longer tolerate the lack of evildoing.
We send them out on a mission so diabolical, so evil that the world will never be the same again!
We order our unworthy followers to
DESTROY THE SATURDAY
and plunge humanity into a dark age of 5 working days and 1 holiday per week... FOREVER!\n\n\n\n\n\n\n\n\n",
'Story2Text', =>
"Whenever EVIL looms on the horizon, the good guys are there to save the day. Son Goku, the protector of Earth and Humanity went to the rescue...
Only to become ROADKILL on his way to the Mortal Szombat tournament! It was Cumi's first time behind the wheel, after all...\n\n\n\n\n\n\n\n\n",
};
$::Hungarian = { 'LanguageCode' => 3,
# In-game text
'MAX COMBO!!!' => "MAX KOMBÓ!!!",
'%d-hit combo!' => '%dX kombó',
'%d%% damage' => '%d%% sebzés',
'Round %d' => '%d. menet',
'REW' => 'VISSZA',
'REPLAY' => undef,
'DEMO' => 'DEMÓ',
'Press F1 to skip...'=> 'F1 gomb: tovább',
'HURRY UP!' => 'GYERÜNK MÁR!',
'TIME IS UP!' => 'NA ENNYI!',
'Final Judgement' => 'Végsõ Ítélet',
'Continue?' => 'Tovább?',
'SPLAT!' => 'FRÖCCS!',
'Choose A Fighter Dammit' => 'Válassz Játékost, Baszki',
# Menu items
"Main Menu" => 'FÕMENÜ',
"~SINGLE PLAYER GAME"=> '~EGYSZEMÉLYES JÁTÉK',
"~MULTI PLAYER GAME"=> '~KÉTSZEMÉLYES JÁTÉK',
"~SURRENDER GAME" => '~JÁTÉK FELADÁSA',
"~OPTIONS" => '~BEÁLLÍTÁSOK',
"~INFO" => '~INFORMÁCIÓK',
"QUIT" => 'QUIT',
"~OK" => '~OKÉ',
"~LANGUAGE: " => '~NYELV: ',
"Options" => 'Beállítások',
"~FULLSCREEN ON" => 'Teljes képernyõ',
"~FULLSCREEN OFF" => 'Ablakos megjelenítés',
"GAME SPEED: " => 'Játék sebesség: ',
"GAME TIME: " => 'Játékidõ: ',
"STAMINA: " => 'Állóképesség: ',
"~SOUND" => '~HANG',
"~RIGHT PLAYER KEYS"=> '~Jobb játékos gombjai',
"~LEFT PLAYER KEYS" => '~Bal játékos gombjai',
"Sound" => 'Hangok',
"CHANNELS: " => 'Csatornák: ',
"SOUND QUALITY: " => 'Hangminõség: ',
"SOUND FIDELITY: " => 'Hangpontosság: ',
"MUSIC VOLUME: " => 'Zene hangereje: ',
"EFFECTS VOLUME: " => 'Zajok hangereje: ',
# Menu options
"BABY" => 'CSECSEMÕ',
"VERY LOW" => 'NAGYON GYÉR',
"LOW" => 'GYÉR',
"NORMAL" => 'NORMÁLIS',
"HIGH" => 'KEMÉNY',
"VERY HIGH" => 'NAGYON KEMÉNY',
"NEAR IMMORTAL" => 'TERMINÁTOR',
"SNAIL RACE" => 'CSIGAVERSENY',
"SLOW" => 'LASSÚ',
"NORMAL" => 'NORMÁL',
"TURBO" => 'TURBÓ',
"KUNG-FU MOVIE" => 'KUNG-FU FILM',
"MONO" => 'MONÓ',
"STEREO" => 'SZTEREÓ',
"LOW" => 'ALACSONY',
"MEDIUM" => 'KÖZEPES',
"HIGH" => 'MAGAS',
"OFF" => 'NINCS',
# Key configuration
'%s player-\'%s\'?' => '%s játékos - \'%s\'?',
'Left' => 'Bal',
'Right' => 'Jobb',
"up", => 'fel',
"down", => 'le',
"left", => 'balra',
"right", => 'jobbra',
"block", => 'védés',
"low punch", => 'alsó ütés',
"high punch", => 'felsõ ütés',
"low kick", => 'alsó rúgás',
"high kick" => 'felsõ rúgás',
'Thanks!' => 'Köszi!',
# Demo screens,
'Fighter Stats' => 'Harcos Adatai',
'Unfortunately this fighter is not yet playable.' => 'Sajnos õ még nem játszható.',
'KEYS' => 'GOMBOK',
'Name: ' => 'Név: ',
'Team: ' => 'Csapat: ',
'Style: ' => 'Stílus: ',
'Age: ' => 'Kor: ',
'Weight: ' => 'Súly: ',
'Height: ' => 'Magasság: ',
'Shoe size: ' => 'Cipõméret: ',
"Credits" => 'Készítették',
"CreditsText1" =>
"A Mortál Szombat Elkövetõi
-- A MORTÁL SZOMBAT CSAPAT --
PROGRAM - UPi
ZENE - Purple Motion
ZENE - XTD / Mystic
GRAFIKA - UPi
Német fordítás - ??
Francia fordítás - Vampyre
Spanyol fordítás - EdispeR
-- SZEREPLÕK --
Boxer - Zoli
Cumi - Mint önmaga
Descant - Mint önmaga
Fûrészes Õrült - Ambrus
Grizli - Mint önmaga
Kinga - Mint önmaga
Macy - Mint önmaga
Misi - Mint önmaga
Rising-san - Surba
Sirpi - Mint önmaga
Taka Ito - Bence
Tökéletlen Katona - Dani
Watasiwa Baka Janajo - Ulmar
Black Dark Evil Mage - UPi
-- SZERVEREINK --
sourceforge.net
apocalypse.rulez.org
freshmeat.net
A Mortál Szombat Copyright 2003, A Mortál Szombat Csapat
Distributed under the GNU General Public Licence Version 2\n\n",
'CreditsText2' =>
'Thanks to Midway for not harrassing us with legal stuff so far, even though '.
'we must surely violate at least 50 of their patents, international copyrights and registered trademarks.
OpenMortal needs your help! If you can contribute music, graphics, improved code, '.
'additional characters, cash, beer, pizza or any other consumable, please mail us '.
'at upi@apocalypse.rulez.org! The same address is currently accepting comments and '.
"fanmail too (hint, hint!).\n\n",
'CreditsText3' =>
"Be sure to check out other stuff from
Apocalypse Production
and
Degec Entertainment\n\n",
'Story1Text' =>
"Mi, a Gonosz Gyilkos Fekete Ellenszenves Sötét(elméjû) Tûzmágusok istenei nem tûrhetjük tovább a gonosztevés hiányát.
Egy küldetést adunk nekik amelyik olyan ördögien gonosz, amilyet a világ még sosem látott!
Méltatlan alattvalóink parancsa:
ELPUSZTÍTANI
A SZOMBATOT
hogy az emberiség az 5 munkanap és egy szünnapos hét sötét korába süllyedjen... ÖRÖKRE!\n\n\n\n\n\n\n\n\n",
'Story2Text', =>
"Whenever EVIL looms on the horizon, the good guys are there to save the day. Son Goku, the protector of Earth and Humanity went to the rescue...
Only to become ROADKILL on his way to the Mortal Szombat tournament! It was Cumi's first time behind the wheel, after all...\n\n\n\n\n\n\n\n\n",
};
$::French = { 'LanguageCode' => 2,
# In-game text
'MAX COMBO!!!' => 'MAX COMBO!!!',
'%d-hit combo!' => '%d-coups combo!', # e.g. 3-hit combo!
'%d%% damage' => '%d%% degats', # e.g. 30% damage
'Round %d' => 'Round %d',
'REW' => 'REMB',
'REPLAY' => 'RALENTI',
'DEMO' => 'DEMO',
'Press F1 to skip...'=> 'Appuyez sur F1 pour annuler',
'HURRY UP!' => 'DEPECHEZ-VOUS !',
'TIME IS UP!' => 'LE TEMPS EST ECOULE',
'Final Judgement' => 'Jugement Final',
'Continue?' => 'Continuer ?',
'SPLAT!' => 'SPLAT',
'Choose A Fighter Dammit' => 'Choisis un combattant Mildiou !',
# Menu items
"Main Menu" => "Menu Principal",
"~SINGLE PLAYER GAME"=> "JEU SOLO",
"~MULTI PLAYER GAME"=> "JEU MULTI",
"~SURRENDER GAME" => "Jeu JeMeRends",
"~OPTIONS" => "OPTIONS",
"~INFO" => "INFO",
"QUIT" => "QUITTER",
"~OK" => "OK",
"~LANGUAGE: " => "LANGUE: ",
"Options" => "Options",
"~FULLSCREEN ON" => "Plein écran ON",
"~FULLSCREEN OFF" => "Plein écran OFF",
"GAME SPEED: " => "Vitesse de jeu: ",
"GAME TIME: " => "Temps de jeu: ",
"STAMINA: " => "Vitalité: ",
"~SOUND" => "SON",
"~RIGHT PLAYER KEYS"=> "Touches joueur droite",
"~LEFT PLAYER KEYS" => "Touches joueur gauche",
"Sound" => 'Son',
"CHANNELS: " => "CANAUX: ", # Mono / Stereo
"SOUND QUALITY: " => "Echantillonage: ", # KHz of playback rate
"SOUND FIDELITY: " => "Qualité sonore: ", # 8 bit or 16 bit
"MUSIC VOLUME: " => "Volume musical: ", # OFF or numeric
"EFFECTS VOLUME: " => "Volume effets : ", # OFF or numeric
# Menu options
"BABY" => "BEBE",
"VERY LOW" => "TRES BAS",
"LOW" => "BAS",
"NORMAL" => "NORMAL",
"HIGH" => "ELEVE",
"VERY HIGH" => "TRES ELEVE",
"NEAR IMMORTAL" => "PRESQUE IMMORTEL",
"SNAIL RACE" => "COURSE D'ESCARGOT",
"SLOW" => "LENT",
"NORMAL" => "NORMAL",
"TURBO" => "TURBO",
"KUNG-FU MOVIE" => "FILM DE KUNG-FU",
# Sound / Channels
"MONO" => "MONO",
"STEREO" => "STEREO",
# Sound / Mixing rate settings
"LOW" => "BAS",
"MEDIUM" => "MOYEN",
"HIGH" => "HAUT",
# Sound volume
"OFF" => "OFF",
# Key configuration
'%s player-\'%s\'?' => '%s joueur-\'%s\'?', # The first %s becomes Left or Right. The second %s is up/down/high punch/...
'Left' => 'Gauche',
'Right' => 'Droite',
"up", => "haut",
"down", => "bas",
"left", => "gauche",
"right", => "droite",
"block", => "bloquer",
"low punch", => "poing bas",
"high punch", => "poing haut",
"low kick", => "Coup de pied bas",
"high kick" => "Coup de pied haut",
'Thanks!' => "Merci !",
# Demo screens,
'Fighter Stats' => 'Statistiques Combattant',
'Unfortunately this fighter is not yet playable.' => 'Malheureusement ce combattant n\'est pas encore jouable',
'Name: ' => 'Nom: ',
'Team: ' => 'Equipe: ',
'Style: ' => 'Style: ',
'Age: ' => 'Age: ',
'Weight: ' => 'Poids: ',
'Height: ' => 'Taille: ',
'Shoe size: ' => 'Pointure: ',
"Credits" => "Crédits",
"CreditsText1" =>
"OPENMORTAL CREDITS
-- L'EQUIPE D'OPENMORTAL EST --
Programmation - UPi
Musique - Purple Motion
Musique - XTD / Mystic
Graphiques - UPi
German translation - ??
French translation - Vampyre
Spanish translation - EdsipeR
Portuguese translation - Vinicius Fortuna
-- Acteurs --
Boxer - Zoli
Cumi - Dans son rôle
Descant - Dans son rôle
Fureszes Orult - Ambrus
Grizli - Dans son rôle
Kinga - Dans son rôle
Macy - Dans son rôle
Misi - Dans son rôle
Rising-san - Surba
Sirpi - Dans son rôle
Taka Ito - Bence
Tokeletlen Katona - Dani
Watasiwa Baka Janajo - Ulmar
Black Dark Evil Mage - UPi
-- HEBERGEMENT --
sourceforge.net
apocalypse.rulez.org
freshmeat.net
OpenMortal est un Copyright 2003 de l'Equipe OpenMortal \
Distribué sous Licence GNU General Public Licence Version 2\n\n",
'CreditsText2' =>
'Merci à Midway de ne pas nous harceler avec des trucs légaux (jusqu\'ici), bien que '.
'nous avons sûrement violé au moins 50 de leurs brevets, copyrights internationaux, et marques déposées.
OpenMortal a besoin de votre aide ! Si vous pouvez contribuer à la musique, graphiques, programmation améliorée, '.
'personnages supplémentaires, du fric, de la bière, des pizzas, ou toute autre forme de nourriture, SVP, envoyez-nous un email '.
'à upi@apocalypse.rulez.org! La même adresse accepte actuellement les commentaires et '.
"les emails de fans aussi (astuce, astuce !).\n\n",
'CreditsText3' =>
"Soyez certains de vérifier les autres programmes de
Apocalypse Production
et
Degec Entertainment\n\n",
'Story1Text' =>
"Nous, les Dieux des Diaboliques Tueurs Noirs Antipathiques Faibles (humour) Mages de Feu, ne tolérons plus longtemps le manque de faiseurs de mal.
Nous les avons une fois de plus envoyés dans un mission si diabolique, si Luciferatique que le monde ne sera plus jamais le même!
Nous ordonnons à nos disciples indignes de
DETRUIRE LE SAMEDI
et de plonger l'humanité dans un Moyen-Age de 5 jours de travail et 1 jour de vacances par semaine... A TOUT JAMAIS!\n\n\n\n\n\n\n\n\n",
'Story2Text', =>
"Bien que le Mal lorgne l'horizon, les gentils doivent sauver le jour ! Son Goku, le protecteur de la Terre et de l'Humanité viennent à la rescousse...
Seulement afin de devenir le TUEUR IMPITOYABLE sur le long chemin du tournoi Mortal Szombat ! C'était la première apparition de Cumi, après tout...\n\n\n\n\n\n\n\n\n",
};
$::Spanish = { 'LanguageCode' => 1,
# In-game text
'MAX COMBO!!!' => 'MAX COMBO!!!',
'%d-hit combo!' => '%d-hit combo!', # e.g. 3-hit combo!
'%d%% damage' => '%d%% daño', # e.g. 30% damage
'Round %d' => 'Round %d',
'REW' => 'RETROCEDER',
'REPLAY' => 'REPRODUCIR',
'DEMO' => 'DEMO',
'Press F1 to skip...'=>'Presiona F1 para saltar...',
'HURRY UP!' => 'APURATE!',
'TIME IS UP!' => 'SE ACABO EL TIEMPO!',
'Final Judgement' => 'Juicio Final',
'Continue?' => 'Continuar?',
'SPLAT!' => 'SPLAT!',
'Choose A Fighter Dammit' => 'Escoge un Peleador',
# Menu items
"Main Menu" => "Menu Principal",
"~SINGLE PLAYER GAME"=>"~1 SOLO JUGADOR ",
"~MULTI PLAYER GAME"=> "~MULTIJUGADOR ",
"~SURRENDER GAME" => "~ENTREGA ",
"~OPTIONS" => "~OPCIONES",
"~INFO" => "~INFO",
"QUIT" => "SALIR",
"~OK" => "~OK",
"~LANGUAGE: " => "~LENGUAJE: ",
"Options" => "Opciones",
"~FULLSCREEN ON" => "~PANTALLA COMPLETA ON",
"~FULLSCREEN OFF" => "~PANTALLA COMPLETA OFF",
"GAME SPEED: " => "VELOCIDAD DEL JUEGO: ",
"GAME TIME: " => "TIEMPO DEL JUEGO: ",
"STAMINA: " => "STAMINA: ",
"~SOUND" => "~SONIDO",
"~RIGHT PLAYER KEYS"=> "~TECLAS JUGADOR DERECHO",
"~LEFT PLAYER KEYS" => "~TECLAS JUGADOR IZQUIERDO",
"Sound" => "Sonido",
"CHANNELS: " => "CANALES: ", # Mono / Stereo
"SOUND QUALITY: " => "CALIDAD DEL SONIDO: ", # KHz of playback rate
"SOUND FIDELITY: " => "FIDELIDAD DEL SONIDO: ", # 8 bit or 16 bit
"MUSIC VOLUME: " => "VOLUMEN DE LA MUSICA: ", # OFF or numeric
"EFFECTS VOLUME: " => "VOLUMEN DE LOS EFECTOS: ", # OFF or numeric
# Menu options
"BABY" => "BEBE",
"VERY LOW" => "MUY LENTO",
"LOW" => "LENTO",
"NORMAL" => "NORMAL",
"HIGH" => "ALTO",
"VERY HIGH" => "MUY ALTO",
"NEAR IMMORTAL" => "CERCA DE LA INMORTALIDAD",
"SNAIL RACE" => "CARRERA DE CARACOL",
"SLOW" => "LENTO",
"NORMAL" => "NORMAL",
"TURBO" => "TURBO",
"KUNG-FU MOVIE" => "PELICULA DE KUNG-FU",
# Sound / Channels
"MONO" => "MONO",
"STEREO" => "STEREO",
# Sound / Mixing rate settings
"LOW" => "BAJO",
"MEDIUM" => "MEDIO",
"HIGH" => "ALTO",
# Sound volume
"OFF" => "OFF",
# Key configuration
'%s player-\'%s\'?' => '%s jugador-\'%s\'?', # The first %s becomes Left or Right. The second %s is up/down/high punch/...
'Left' => 'Izquierda',
'Right' => 'Derecha',
"up", => "arriba",
"down", => "abajo",
"left", => "izquierda",
"right", => "derecha",
"block", => "bloquear",
"low punch", => "puño bajo",
"high punch", => "puño alto",
"low kick", => "patada baja",
"high kick" => "patada alta",
'Thanks!' => 'Gracias!',
# Demo screens,
'Fighter Stats' => 'Estadísticas del Peleador',
'Unfortunately this fighter is not yet playable.' => 'Desafortunadamente este jugador no esta habilitado.',
'Name: ' => 'Nombre: ',
'Team: ' => 'Equipo: ',
'Style: ' => 'Estilo: ',
'Age: ' => 'Edad: ',
'Weight: ' => 'Peso: ',
'Height: ' => 'Altura: ',
'Shoe size: ' => 'Tamaño de zapato: ',
"Credits" => "Creditos",
"CreditsText1" =>
"CREDITOS
-- EL EQUIPO DE OPENMORTAL ES --
CODING - UPi
MUSIC - Purple Motion
MUSIC - XTD / Mystic
GRAPHICS - UPi
German translation - ??
French translation - Vampyre
Spanish translation - EdsipeR
Portuguese translation - Vinicius Fortuna
-- CAST --
Boxer - Zoli
Cumi - como el mismo
Descant - como el mismo
Fureszes Orult - Ambrus
Grizli - como el mismo
Kinga - como ella misma
Macy - como ella misma
Misi - como el mismo
Rising-san - Surba
Sirpi - como el mismo
Taka Ito - Bence
Tokeletlen Katona - Dani
Watasiwa Baka Janajo - Ulmar
Black Dark Evil Mage - UPi
-- HOSTING --
sourceforge.net
apocalypse.rulez.org
freshmeat.net
OpenMortal es Marca registrada 2003 de OpenMortal Team\
Distribuido bajo la GNU General Public Licence Version 2\n\n",
'CreditsText2' =>
'Gracias a Midway por no aplastarnos con su equipo legal'.
'Seguramente nosotros violamos al menos 50 de sus patentes, marcas registradas internacionales y trademarks registradas .
OpenMortal necesita de tu ayuda! Si tu puedes contribuir con musica, imagenes, improvisación de codigo, '.
'caracteres adicionales, dinero, cerveza, pizza o cualquier otra cosa consumible, por favor escribenos '.
'a upi@apocalypse.rulez.org! La misma dirección esta actualmente aceptando comentarios y '.
"tambien emails de fans (hint, hint!).\n\n",
'CreditsText3' =>
"Estate atento para verificar otro stuff desde
Apocalypse Production
y
Degec Entertainment\n\n",
'Story1Text' =>
"Nosotros, los Dioses del Endemoniado Negro Antipatico Asesino Dim no tolero mas lo endemoniado.
Enviamos a ellos en una misión tan diabólica, tan endemoniada que el mundo nunca será el mismo de nuevo!
Ordenamos a nuestros indignos seguidores a
DESTRUIR EL SABADO
y plagar la humanidad en una epoca oscura de 5 dias de trabajo y uno de descanso por semana.... POR SIEMPRE!\n\n\n\n\n\n\n\n\n",
'Story2Text', =>
"Whenever EVIL looms on the horizon, the good guys are there to save the day. Son Goku, the protector of Earth and Humanity went to the rescue...
Only to become ROADKILL on his way to the Mortal Szombat tournament! It was Cumi's first time behind the wheel, after all...\n\n\n\n\n\n\n\n\n",
};
$::Portuguese = { 'LanguageCode' => 4,
# In-game text
'MAX COMBO!!!' => 'COMBO MÁXIMO!!!',
'%d-hit combo!' => 'Combo de %d golpes!',
'%d%% damage' => 'Dano de %d%%',
'Round %d' => 'Round %d',
'REW' => 'REBOBINANDO',
'REPLAY' => 'REPLAY',
'DEMO' => 'DEMO',
'Press F1 to skip...' => 'Pressione F1 para resumir...',
'HURRY UP!' => 'APRESSEM-SE!',
'TIME IS UP!' => 'TEMPO ESGOTADO!',
'Final Judgement' => 'Julgamento Final',
'Continue?' => 'Continuar?',
'SPLAT!' => 'SPLAT!',
'Choose A Fighter Dammit' => 'Escolha um Lutador',
# Menu items
"Main Menu" => "Menu Principal",
"~SINGLE PLAYER GAME" => "JOGO ~INDIVIDUAL",
"~MULTI PLAYER GAME" => "JOGO ~MULTIPLAYER" ,
"~SURRENDER GAME" => "~Entregar-se",
"~OPTIONS" => "~OPÇÕES",
"~INFO" => "IN~FORMAÇÕES",
"QUIT" => "SAIR",
"~OK" => "~OK",
"~LANGUAGE: " => "~Língua: ",
"Options" => "Opções",
"~FULLSCREEN ON" => "TELA CHEIA ATIVADA",
"~FULLSCREEN OFF" => "TELA CHEIA DESATIVADA",
"GAME SPEED: " => "VELOCIDADE DE JOGO: ",
"GAME TIME: " => "TEMPO DE JOGO: ",
"STAMINA: " => "RESISTÊNCIA: ",
"~SOUND" => "~AUDIO",
"~RIGHT PLAYER KEYS" => "TECLAS, JOGADOR DA ~DIREITA",
"~LEFT PLAYER KEYS" => "TECLAS, JOGADOR DA ~ESQUERDA",
"Sound" => "Audio",
"CHANNELS: " => "CANAIS: ", # Mono / Stereo
"SOUND QUALITY: " => "QUALIDADE DE SOM: ", # KHz of playback rate
"SOUND FIDELITY: " => "FIDELIDADE DE SOM: ",# 8 bit or 16 bit
"MUSIC VOLUME: " => "VOLUME DA MÚSICA: ", # OFF or numeric
"EFFECTS VOLUME: " => "VOLUME DOS EFEITOS: ", # OFF or numeric
# Menu options
"BABY" => "BEBÊ",
"VERY LOW" => "MUITO BAIXA",
"LOW" => "BAIXA",
"NORMAL" => "NORMAL",
"HIGH" => "ALTA",
"VERY HIGH" => "MUITO ALTA",
"NEAR IMMORTAL" => "QUASE IMORTAL",
"SNAIL RACE" => "CORRIDA DE LESMAS",
"SLOW" => "DEVAGAR",
"NORMAL" => "NORMAL",
"TURBO" => "TURBO",
"KUNG-FU MOVIE" => "FILME DE KUNG-FU",
# Sound / Channels
"MONO" => "MONO",
"STEREO" => "ESTÉREO",
# Sound / Mixing rate settings
# CLASHES WITH MENU OPTIONS!!!
"LOW" => "BAIXA",
"MEDIUM" => "MÉDIA",
"HIGH" => "ALTA",
# Sound volume
"OFF" => "DESLIGADO",
# Key configuration
'%s player-\'%s\'?' => 'Jogador da %s-\'%s\'?', # The first %s becomes Left or Right. The second %s is up/down/high punch/...
'Left' => 'Esquerda',
'Right' => 'Direita',
"up", => 'cima',
"down", => 'baixo',
"left", => 'esquerda',
"right", => 'direita',
"block", => 'defesa',
"low punch", => 'soco baixo',
"high punch", => 'soco alto',
"low kick", => 'chute baixo',
"high kick" => 'chute alto',
'Thanks!' => 'Obrigado!',
# Demo screens,
'Fighter Stats' => 'Ficha Técnica',
'Unfortunately this fighter is not yet playable.' => 'Infelizmente ainda não é possível jogar com esse lutador',
'Name: ' => 'Nome: ',
'Team: ' => 'Time: ',
'Style: ' => 'Estilo: ',
'Age: ' => 'Idade: ',
'Weight: ' => 'Peso: ',
'Height: ' => 'Altura: ',
'Shoe size: ' => 'Calçado: ',
"Credits" => 'Créditos',
"CreditsText1" =>
"CRÉDITOS DE OPENMORTAL
-- EQUIPE OPENMORTAL --
PROGRAMADOR - UPi
MÚSICA - Purple Motion
MÚSICA - XTD / Mystic
GRÁFICOS - UPi
Tradução para o Alemão - ??
Tradução para o Francês - Vampyre
Tradução para o Espanhol - EdsipeR
Tradução para o Português - Vinicius Fortuna
-- ELENCO --
Boxer - Zoli
Cumi - ele mesmo
Descant - ele mesmo
Fureszes Orult - Ambrus
Grizli - ele mesmo
Kinga - ela mesma
Macy - ela mesma
Misi - ele mesmo
Rising-san - Surba
Sirpi - ele mesmo
Taka Ito - Bence
Tokeletlen Katona - Dani
Watasiwa Baka Janajo - Ulmar
Black Dark Evil Mage - UPi
-- HOSPEDAGEM --
sourceforge.net
apocalypse.rulez.org
freshmeat.net
OpenMortal é marca registrada de 2003 da Equipe OpenMortal
Distribuído segundo a Licença Pública Geral GNU Versão 2\n\n",
'CreditsText2' =>
'Obrigado à Midway por não nos ameaçar com assuntos legais até agora, apesar '.
'de nós certamente termos violado pelo menos umas 50 de suas patentes, direitos de cópia internacionais e marcas registradas.
OpenMortal precisa de sua ajuda! Se você puder contribuir com músicas, gráicos, códigos aperfeiçoados, '.
'personagens extras, dinheiro, cerveja, pizza ou qualquer outro bem-de-consumo, por favor escreva para nós em upi@apocalypse.rulez.org! '.
"Atualmente o mesmo endereço também está aceitando comentários e mensagens de fãs.\n\n",
'CreditsText3' =>
"Não se esqueça de dar uma olhada nos outros produtos da
Apocalypse Production
e da Degec Entertainment\n\n",
'Story1Text' =>
"Nós, os Deuses dos Magos de Fogo Malignos Assassinos Sombrios Antipáticos Estúpidos não mais toleramos a falta de maldade no mundo.
Nós os mandamos em uma missão tão diabólica, tão maligna, que o mundo nunca mais será o mesmo!
Nós ordenamos nossos indignos seguidores que
DESTUÍSSEM O SÁBADO,
condenando a humanidade a uma era sombria, com 5 dias úteis e apenas 1 dia de folga por semana... PARA SEMPRE!\n\n\n\n\n\n\n\n\n",
'Story2Text', =>
"Sempre que o Mal surge no horizonte, os heróis estão lá para salvar o dia. Son Goku, o protetor da Terra e da Humanidade, veio então para nos salvar...
Apenas para ser MORTO ATROPELADO em seu caminho para o torneio de Mortal Szombat! Bem, afinal era o primeiro dia de direção de Cumi...\n\n\n\n\n\n\n\n\n",
};
%::LanguageCodes =
(
'en' => $::English,
'hu' => $::Hungarian,
'fr' => $::French,
'es' => $::Spanish,
'pt' => $::Portuguese,
);
=comment
SetLanguage sets the two-character language code.
=cut
$::Language = $::English;
$::Language = $::Hungarian;
$::Language = $::French;
$::Language = $::Spanish;
$::Language = $::Portuguese;
sub SetLanguage($)
{
my ($NewLanguageCode) = @_;
$::LanguageCode = $NewLanguageCode;
if ( defined $::LanguageCodes{$::LanguageCode} )
{
$::Language = $::LanguageCodes{$::LanguageCode};
}
else
{
print "Language $::LanguageCode is not available. Reverting to English.\n";
$::Language = $::English;
$::LanguageCode = 'en';
}
$::LanguageNumber = $::Language->{'LanguageCode'};
}
sub Translate($)
{
my $text = shift;
# print "The translation of '$text' is ";
$text = $::Language->{$text} if defined $::Language->{$text};
# print "'$text'.\n";
$::Translated = $text;
}
1;
# "Connect to game", "Create game"
# "Network mode: ", "Connect to: ", "Start Network Play!", "Cancel"
# "Connection closed."
# "Press Escape to abort"
# "Press Escape for the menu"
-# "You must have port 14882 open for this to work."
\ No newline at end of file
+# "You must have port 14882 open for this to work."

File Metadata

Mime Type
text/x-diff
Expires
Fri, Sep 12, 1:29 AM (22 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
42790
Default Alt Text
(99 KB)

Event Timeline