Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F102577
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
32 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/data/script/Backend.pl b/data/script/Backend.pl
index 299a677..7a357da 100644
--- a/data/script/Backend.pl
+++ b/data/script/Backend.pl
@@ -1,593 +1,594 @@
#!/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
$GAMEBITS = 3; # Number of oversampling bits in coordinates
$GAMEBITS2 = 1 << $GAMEBITS; # 2^(oversampling)
$SCRWIDTH = 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 figthers.
$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;
require 'PlayerInput.pl';
require 'Fighter.pl';
require 'FighterStats.pl';
require 'Doodad.pl';
require 'Keys.pl';
require 'State.pl';
=comment
#require 'Kolos.pl';
require 'Zoli.pl';
require 'UPi.pl';
#require 'Ulmar.pl';
#require 'Cumi.pl';
print "Kolos frames :", scalar @KolosFrames,"\n";
print "Kolos states :", scalar keys %KolosStates,"\n";
print "Zoli frames :", scalar @ZoliFrames,"\n";
print "Zoli states :", scalar keys %ZoliStates,"\n";
print "UPi frames :", scalar @UPiFrames,"\n";
print "UPi states :", scalar keys %UPiStates,"\n";
print "Ulmar frames :", scalar @UlmarFrames,"\n";
print "Ulmar states :", scalar keys %UlmarStates,"\n";
print "Cumi frames :", scalar @CumiFrames,"\n";
print "Cumi states :", scalar keys %CumiStates,"\n";
=cut
=comment
MAIN OBJECTS
=cut
$Fighter1 = Fighter->new();
$Fighter2 = Fighter->new();
$Fighter1->{NUMBER} = 0;
$Fighter1->{OTHER} = $Fighter2;
$Fighter2->{NUMBER} = 1;
$Fighter2->{OTHER} = $Fighter1;
@Fighters = ( $Fighter1, $Fighter2 );
=comment
VARIABLES FOR DRAWING THE SCENE
=cut
$p1x = 10; # Player 1 X position
$p1y = 100; # Player 1 Y position
$p1f = 10; # Player 1 frame
$p1h = 100; # Player 1 HP
$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
$bgx = 0; # Background X position
$bgy = 0; # Background Y position
$time = 0; # Current game time
$over = 0; # Is the game over?
$ko = 0; # Is one fighter knocked down?
$dx = 0; # Doodad X position
$dy = 0; # Doodad Y position
$dt = 0; # Doodad type
$df = 0; # Doodad frame number
=comment
JUDGEMENT METHODS
=cut
sub JudgementStart
{
$bgx = 0;
$bgy = ( $SCRHEIGHT2 - $BGHEIGHT2 ) >> 1;
$BgSpeed = 0;
$BgPosition = 0;
$BgScrollEnabled = 0;
$OverTimer = 0;
$JudgementMode = 1;
$Debug = 0;
($JudgementWinner) = @_;
ResetEarthquake();
$time = 0;
$over = 0;
$Fighter1->Reset();
$Fighter1->{X} = ($JudgementWinner ? 150 : 520 ) * $GAMEBITS2;
$Fighter1->{DIR} = ($JudgementWinner ? 1 : -1 );
$Fighter1->{NEXTST} = 'Stand';
$Fighter1->Update();
$Fighter2->Reset();
$Fighter2->{X} = ($JudgementWinner ? 520 : 150 ) * $GAMEBITS2;
$Fighter2->{DIR} = ($JudgementWinner ? -1 : 1 );
$Fighter2->{NEXTST} = 'Stand';
$Fighter2->Update();
$Input1->Reset();
$Input2->Reset();
}
=comment
PLAYER SELECTION METHODS
=cut
sub SelectStart
{
$bgx = 0;
$bgy = ( $SCRHEIGHT2 - $BGHEIGHT2 ) >> 1;
$BgSpeed = 0;
$BgPosition = 0;
$BgScrollEnabled = 0;
$OverTimer = 0;
$JudgementMode = 0;
$Debug = 0;
ResetEarthquake();
$time = 0;
$over = 0;
$Fighter1->Reset(); #\@ZoliFrames,\%ZoliStates);
$Fighter1->{X} = 80 * $GAMEBITS2;
$Fighter1->{NEXTST} = 'Stand';
$Fighter1->Update();
$Fighter2->Reset(); #\@ZoliFrames,\%ZoliStates);
$Fighter2->{X} = 560 * $GAMEBITS2;
$Fighter2->{NEXTST} = 'Stand';
$Fighter2->Update();
$Input1->Reset();
$Input2->Reset();
}
sub SetPlayerNumber
{
my ($player, $number) = @_;
my ($f);
$f = $player ? $Fighter2 : $Fighter1;
if ( $number eq 1 ) { $f->Reset('Ulmar', \@UlmarFrames,\%UlmarStates); }
elsif ( $number eq 2 ) { $f->Reset('UPi', \@UPiFrames,\%UPiStates); }
elsif ( $number eq 3 ) { $f->Reset('Zoli', \@ZoliFrames,\%ZoliStates); }
elsif ( $number eq 4 ) { $f->Reset('Cumi', \@CumiFrames,\%CumiStates); }
elsif ( $number eq 5 ) { $f->Reset('Sirpi', \@SirpiFrames,\%SirpiStates); }
elsif ( $number eq 6 ) { $f->Reset('Maci', \@MaciFrames,\%MaciStates); }
elsif ( $number eq 7 ) { $f->Reset('Bence', \@BenceFrames,\%BenceStates); }
elsif ( $number eq 9 ) { $f->Reset('Descant', \@DescantFrames,\%DescantStates); }
elsif ( $number eq 8 ) { $f->Reset('Grizli', \@GrizliFrames,\%GrizliStates); }
else {
# Fallback
$f->Reset('Zoli', \@ZoliFrames,\%ZoliStates);
}
$f->{NEXTST} = 'Stand';
$f->Update();
$::PlayerName = $::FighterStats[$number]->{NAME};
}
sub PlayerSelected
{
my ($number) = @_;
my ($f) = $number ? $Fighter2 : $Fighter1;
$f->Event( 'Won' );
$f->Update();
}
=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;
$bgx -= $QuakeX;
$bgy -= $QuakeY;
$p1x += $QuakeX;
$p1y += $QuakeY;
$p2x += $QuakeX;
$p2y += $QuakeY;
# Do not quake doodads for now.
}
=comment
GAME BACKEND METHODS
=cut
sub GameStart
{
my ( $MaxHP, $debug ) = @_;
$bgx = ( $SCRWIDTH2 - $BGWIDTH2) >> 1;
$bgy = ( $SCRHEIGHT2 - $BGHEIGHT2 ) >> 1;
$BgSpeed = 0;
$BgPosition = $BgMax >> 1;
$BgScrollEnabled = 1;
$HitPointScale = 1000 / $MaxHP;
$Debug = $debug;
ResetEarthquake();
$time = 0;
$Fighter1->Reset();
$Fighter1->{HP} = $MaxHP;
$Input1->Reset();
$Fighter2->Reset();
$Fighter2->{HP} = $MaxHP;
$Input2->Reset();
$over = 0;
$ko = 0;
$OverTimer = 0;
$JudgementMode = 0;
@Doodads = ();
@Sounds = ();
$p1h = $p2h = 0;
}
sub GetFighterData
{
my ($fighter) = @_;
my ($x, $y, $fnum, $f, $hp);
$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};
}
return ($x, $y, $fnum);
}
sub GetNextDoodadData
{
if ( $NextDoodad >= scalar @Doodads )
{
$doodad_x = $doodad_y = $doodad_t = $doodad_f = -1;
return;
}
my ($doodad) = $Doodads[$NextDoodad];
$doodad_x = $doodad->{X} / $GAMEBITS2 - $bgx;
$doodad_y = $doodad->{Y} / $GAMEBITS2 - $bgy;
$doodad_t = $doodad->{T};
$doodad_f = $doodad->{F};
$doodad_text = $doodad->{TEXT};
++$NextDoodad;
}
sub UpdateDoodads
{
my ($i, $j, $doodad);
for ($i=0; $i<scalar @Doodads; ++$i)
{
$doodad = $Doodads[$i];
$j = UpdateDoodad( $doodad );
if ( $j )
{
# 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 DoFighterEvents
{
my ($fighter, $hit) = @_;
if ( $JudgementMode )
{
if ( $fighter->{NUMBER} == $JudgementWinner )
{
$fighter->Event("Won");
}
else
{
$fighter->Event("Hurt");
}
return;
}
if ( $hit )
{
$fighter->HitEvent( $fighter->{OTHER}->GetCurrentState()->{HIT}, $hit );
return;
}
#if ( ($fighter->{X} - $fighter->{OTHER}->{X}) * ($fighter->{DIR}) > 0 )
if ( $fighter->IsBackTurned )
{
$fighter->Event("Turn");
}
if ( $fighter->{OTHER}->{ST} eq 'Dead' )
{
$fighter->Event("Won");
}
if ( ($fighter->{IDLE} > 150) and (rand(350) < 1) )
{
$fighter->Event("Fun");
}
if ( ($fighter->{IDLE} > 150) and (rand(350) < 1) )
{
$fighter->Event("Threat");
}
}
sub GameAdvance
{
# $::adv += 1;
# return if ( $::adv % 3 );
my ($hit1, $hit2);
$time += 4/1000;
$NextDoodad = 0;
$NextSound = 0;
# 1. ADVANCE THE PLAYERS
$Input1->Advance();
$Input2->Advance();
$Fighter1->Advance( $Input1 );
$Fighter2->Advance( $Input2 );
$hit2 = $Fighter1->CheckHit();
$hit1 = $Fighter2->CheckHit();
# 2. Events come here
DoFighterEvents( $Fighter1, $hit1 );
DoFighterEvents( $Fighter2, $hit2 );
UpdateDoodads();
$Fighter1->Update();
$Fighter2->Update();
if ( $OverTimer == 0 and
($Fighter1->{ST} eq 'Dead' or $Fighter1->{ST} eq 'Won2') and
($Fighter2->{ST} eq 'Dead' or $Fighter2->{ST} eq 'Won2') )
{
$OverTimer = 1;
}
elsif ( $OverTimer > 0 )
{
$OverTimer++;
$over = 1 if ( $OverTimer > 200 )
}
# 3. DO THE BACKGROUND SCROLLING THING
if ( $BgScrollEnabled )
{
$BgOpt = ($Fighter1->{X} + $Fighter2->{X}) / 2; # 1/1 Stupid kdevelop syntax hightlight.
if ( ($BgOpt - $BgSpeed*50 - $BgPosition) > (320 << $GAMEBITS)) { $BgSpeed++; }
if ( ($BgOpt - $BgSpeed*50 - $BgPosition) < (320 << $GAMEBITS)) { $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) = GetFighterData( $Fighter1 );
($p2x, $p2y, $p2f) = GetFighterData( $Fighter2 );
$phTarget = $Fighter1->{HP}*$HitPointScale;
if ( $p1h < $phTarget ) { $p1h += 5; }
if ( $p1h > $phTarget ) { $p1h -= 3; }
$p1h = $phTarget if abs($p1h - $phTarget) < 3;
$p1h = 0 if $p1h < 0;
$phTarget = $Fighter2->{HP}*$HitPointScale;
if ( $p2h < $phTarget ) { $p2h += 5; }
if ( $p2h > $phTarget ) { $p2h -= 3; }
$p2h = $phTarget if abs($p2h - $phTarget) < 3;
$p2h = 0 if $p2h < 0;
$bgx = $BgPosition >> $GAMEBITS;
$p1x -= $bgx;
$p2x -= $bgx;
$bgy = 0;
AdvanceEarthquake();
# 5. DEBUG POLYGONS
return unless $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/Fighter.pl b/data/script/Fighter.pl
index c825bb6..8246a1d 100644
--- a/data/script/Fighter.pl
+++ b/data/script/Fighter.pl
@@ -1,740 +1,792 @@
require 'Collision.pl';
require 'DataHelper.pl';
require 'Damage.pl';
package Fighter;
use strict;
=comment
Fighter's members are:
NAME string The name of the character, e.g. "Ulmar".
FRAMES array The character's frame description.
STATES hash The character's state description.
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 hash A reference to the other Fighter
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 Reset {
my ($self, $name, $frames, $states) = @_;
die "Insufficient parameters." unless defined $self;# and defined $frames and defined $states;
my ($number);
$number = $self->{NUMBER};
print STDERR "Resetting fighter $number\n";
$self->{NAME} = $name if defined $name;
$self->{FRAMES} = $frames if defined $frames;
$self->{STATES} = $states if defined $states;
$self->{X} = (( $number ? 540 : 100 ) << $::GAMEBITS) + $::BgPosition;
$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;
}
=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) = @_;
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};
$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 ( $self->{Y} >= $::GROUND2 and
$self->{OTHER}->{Y} >= $::GROUND2 )
{
my ( $centerX, $centerOtherX, $pushDir );
$centerX = $self->GetCenterX;
$centerOtherX = $self->{OTHER}->GetCenterX;
if ( abs($centerX - $centerOtherX) < 60 )
{
$pushDir = ($centerX > $centerOtherX) ? 1 : -1;
$self->{X} += 10 * $pushDir;
$self->{OTHER}->{X} -= 10 * $pushDir;
}
}
# 2.4. HORIZONTAL BOUNDS CHECKING
$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
if ( $st->{SITU} eq 'Falling')
{
::AddEarthquake( $self->{PUSHY} / 20 );
push @::Sounds, ('splat.wav') 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;
$self->{NEXTST} = 'Getup';
$self->{NEXTST} = 'Dead' if $self->{HP} <= 0;
}
return;
}
else
{
push @::Sounds, ('thump.wav');
$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};
$dist = ($self->{OTHER}->{X} - $self->{X}) * $self->{DIR};
+ undef $con if $::ko;
if ( defined $con )
{
$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;
}
}
$self->{'NEXTST'} = $nextst;
}
sub CheckHit
{
my ($self) = @_;
return 0 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
$i,
);
$st = $self->{STATES}->{$self->{ST}};
return 0 unless $st->{HIT};
return 0 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 );
$i = $self->{OTHER}->IsHitAt( \@hit );
$self->{DELIVERED} = 1 if $i;
print "Collision = $i; ";
if ( $i == 0 )
{
# Make the 'Woosh' sound - maybe.
$nextst = $st->{NEXTST};
print "NEXTST = $nextst";
$nextst = $self->{STATES}->{$nextst};
print "NEXTST->HIT = ", $nextst->{HIT}, "\n";
push @::Sounds, ('woosh.wav') unless $nextst->{HIT} and defined $self->{FRAMES}->[$nextst->{F}]->{hit};
}
return $i;
}
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 '' )
{
if ( $event =~ /Hurt|Threat|Fun|Turn/ )
{
if ( $event eq 'Fun' and defined $self->{STATES}->{'Funb'} and rand() < 0.5 )
{
$event = 'Funb';
}
$self->{NEXTST} = $event;
}
}
}
=comment
Event($self, $event, $eventpar): Handles the following events:
'Highhit', 'Uppercut', 'Hit', 'Groinhit', 'Leghit', 'Fall'
=cut
sub HitEvent($$$)
{
my ($self, $event, $eventpar) = @_;
my ($st, $blocked, $damage);
$st = $self->GetCurrentState();
# Do events: Highhit, Uppercut, Hit, Groinhit, Leghit, Fall
$eventpar = '' unless defined $eventpar; # Supress useless warning
- $damage = ::GetDamage( $self->{OTHER}->{NAME}, $self->{OTHER}->{ST} );
+ if ( $eventpar eq 'Maxcombo' ) { $damage = 0; }
+ else { $damage = ::GetDamage( $self->{OTHER}->{NAME}, $self->{OTHER}->{ST} ); }
$blocked = $st->{BLOCK};
$blocked = 0 if ( $self->IsBackTurned() );
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} > $self->{OTHER}->{X} ) ? -1 : 1;
# Handle the unfortunate event of the player "dying".
if ( $self->{HP} <= 0 )
{
push @::Sounds, ('bowling.voc');
$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;
return;
}
# Handle blocked attacks.
if ( $blocked )
{
push @::Sounds, ('thump.wav');
- $self->{PUSHX} = - $damage * 5 * $self->{DIR};
+ $self->HitPush( - $damage * 20 * $self->{DIR} );
+ # $self->{PUSHX} = - $damage * 5 * $self->{DIR};
return;
}
# Handle the rest of the events.
if ( $event eq 'Uppercut' )
{
push @::Sounds, ('evil_laughter.voc');
::AddEarthquake( 20 );
}
elsif ($event eq 'Groinhit') { push @::Sounds, ('woman_screams.voc'); }
{ push @::Sounds, ('thump3.voc'); }
$self->{COMBO} += 1;
$self->{COMBOHP} += $damage;
$damage *= $self->{COMBO}; # Only for the purpose of pushing
+ if ( $self->{COMBO} >= $::MAXCOMBO )
+ {
+ $self->ComboEnds();
+ $event = 'Uppercut';
+ $self->{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->{PUSHX} = - $damage * 20 * $self->{DIR};
+ $self->HitPush( - $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' )
{
$self->{NEXTST} = 'HighPunched';
- $self->{PUSHX} = - $damage * 20 * $self->{DIR};
+ $self->HitPush( - $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->{PUSHX} = - $damage * 20 * $self->{DIR};
+ $self->HitPush( - $damage * 20 * $self->{DIR} );
+ # $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
elsif ( $event eq 'Groinhit' )
{
$self->{NEXTST} = 'GroinKicked';
- $self->{PUSHX} = - $damage * 20 * $self->{DIR};
+ $self->HitPush( - $damage * 20 * $self->{DIR} );
+ # $self->{PUSHX} = - $damage * 20 * $self->{DIR};
}
elsif ( $event eq 'Leghit' )
{
$self->{NEXTST} = 'Swept';
- $self->{PUSHX} = - $damage * 20 * $self->{DIR};
+ $self->HitPush( - $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;
- if ($self->{COMBO})
+ return unless $combo;
+
+ if ( $self->{COMBO} > 1 )
{
- if ( $self->{COMBO} > 1 )
- {
- my ( $head, $doodad, $x, $y );
- $head = $self->{FRAMES}->[$self->{FR}]->{head};
- $x = $self->{X} + $head->[0] * $::GAMEBITS2 * $self->{DIR};
- $y = $self->{Y} + $head->[1] * $::GAMEBITS2;
- $doodad = ::CreateTextDoodad( $x, $y - 30 * $::GAMEBITS2,
- $self->{NUMBER},
- $self->{COMBO} . "-hit combo!" );
- $doodad->{LIFETIME} = 80;
- $doodad->{SY} = -3;
- $doodad->{SX} = -3;
-
- $doodad = ::CreateTextDoodad( $x, $y - 10 * $::GAMEBITS2,
- $self->{NUMBER},
- int($self->{COMBOHP}*$::HitPointScale/10) . "% damage" );
- $doodad->{LIFETIME} = 80;
- $doodad->{SY} = -3;
- $doodad->{SX} = +3;
-
- print $self->{COMBO}, "-hit combo for ", $self->{COMBOHP}, " damage.\n";
-
- push @::Sounds, ('ba_gooock.voc');
- }
+ my ( $head, $doodad, $x, $y, $combotext );
+
+ $combotext = $ismaxcombo ? "MAX COMBO!!!" : ($self->{COMBO} . "-hit combo!");
+
+ $head = $self->{FRAMES}->[$self->{FR}]->{head};
+ $x = $self->{X} + $head->[0] * $::GAMEBITS2 * $self->{DIR};
+ $y = $self->{Y} + $head->[1] * $::GAMEBITS2;
+ $doodad = ::CreateTextDoodad( $x, $y - 30 * $::GAMEBITS2,
+ $self->{NUMBER},
+ $combotext );
+ $doodad->{LIFETIME} = $ismaxcombo ? 120 : 80;
+ $doodad->{SY} = -3;
+ $doodad->{SX} = -3;
- $self->{COMBO} = 0;
- $self->{COMBOHP} = 0;
+ $doodad = ::CreateTextDoodad( $x, $y - 10 * $::GAMEBITS2,
+ $self->{NUMBER},
+ int($self->{COMBOHP}*$::HitPointScale/10) . "% damage" );
+ $doodad->{LIFETIME} = 80;
+ $doodad->{SY} = -3;
+ $doodad->{SX} = +3;
+
+ print $self->{COMBO}, "-hit combo for ", $self->{COMBOHP}, " damage.\n";
+ push @::Sounds, ( $ismaxcombo ? 'crashhh.voc' : 'ba_gooock.voc');
}
+
+ $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 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;
# 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, $ddata);
$frame = $self->{FRAMES}->[$self->{FR}];
$hit = $frame->{hit};
if ( defined $hit )
{
$ddata = $st->{DOODAD};
$doodad = ::CreateDoodad(
$self->{X} + $hit->[0] * $::GAMEBITS2 * $self->{DIR},
$self->{Y} + $hit->[1] * $::GAMEBITS2,
$ddata->{W},
$ddata->{H},
$ddata->{T},
$self->{NUMBER} );
$doodad->{SX} = $ddata->{SX} * $self->{DIR};
$doodad->{SY} = $ddata->{SY} * $self->{DIR};
$doodad->{FRAMES} = $ddata->{FRAMES};
$doodad->{SA} = $ddata->{SA};
}
}
}
+
+=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, $pushforce) = @_;
+ if ( $self->IsCornered )
+ {
+ $self->{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
+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;
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, Feb 4, 2:14 PM (11 h, 15 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55648
Default Alt Text
(32 KB)
Attached To
Mode
R76 OpenMortal
Attached
Detach File
Event Timeline
Log In to Comment