Game-Xomb

 view release on metacpan or  search on metacpan

lib/Game/Xomb.pm  view on Meta::CPAN

        } else {
            # KLUGE avoid busy-wait on "tail" of an active savegame
            sleep(0.2);
        }
    }
    print $Save_FH $key if defined $Save_FH;
    return $key;
}

sub report_position {
    log_message('Transponder reports ['
          . join(',', $Animates[HERO][LMC][WHERE]->@[ PCOL, PROW ])
          . ']');
    return MOVE_FAILED, 0;
}

sub report_version {
    log_message('Xomb v' . $VERSION . ' seed ' . $Seed . ' turn ' . $Turn_Count);
    return MOVE_FAILED, 0;
}

sub restore_term {
    ReadMode 'restore';
    print TERM_NORM, SHOW_CURSOR, UNALT_SCREEN;
}

sub rubble_delay {
    my ($ani, $cost) = @_;
    if (coinflip()) {
        if ($ani->[SPECIES] == HERO) {
            # Ultima IV does this. too annoying?
            $Violent_Sleep_Of_Reason = 1;
            log_message('Slow progress!');
        }
        return ($cost >> 1) + 2 + irand(4);
    } else {
        return 2 + irand(4);
    }
}

{
    my $energy = '00';

    sub sb_update_energy {
        $energy = sprintf "%02d", $Animates[HERO][STASH][ECOST];
    }

    sub show_status_bar {
        print at_row(STATUS_ROW),
          sprintf('Level %02d t', $Level), $energy, TERM_NORM,
          display_hitpoints(), display_cellobjs(), display_shieldup();
    }
}

sub score {
    my ($won) = @_;
    my $score = loot_value() + ($won ? 10000 : 0) + 10 * int exp $Level_Max;
    return "Score: $score in $Turn_Count turns (v$VERSION:$Seed)";
}

sub update_gameover {
    state $count = 0;
    raycast_fov(1);
    tcflush(STDIN_FILENO, TCIFLUSH);
    my $key = $RKFN->(\%Key_Commands);
    if ($count == 4) {
        has_lost();
    } elsif ($count >= 2) {
        print AT_MSG_ROW, CLEAR_RIGHT, '-- press Esc to continue --';
        has_lost() if $key eq "\033" or $key eq 'q';
    } elsif ($count == 1) {
        log_message('Communication lost with remote unit.');
    }
    $count++;
    return MOVE_OKAY, DEFAULT_COST;
}

sub update_fungi {
    my ($self) = @_;
    my ($mcol, $mrow) = $self->[LMC][WHERE]->@[ PCOL, PROW ];
    my ($tcol, $trow) = $Animates[HERO][LMC][WHERE]->@[ PCOL, PROW ];
    my $weap = $self->[STASH][WEAPON];

    my ($hits, $cost) = does_hit(distance($mcol, $mrow, $tcol, $trow), $weap);
    return MOVE_OKAY, $cost if $hits == -1;

    my (@burned, @path);
    $hits = 0;
    walkcb(
        sub {
            my ($col, $row, $iters) = @_;
            my $lmc = $LMap[$row][$col];
            push @path, [ $col, $row ];
            if (defined $lmc->[ANIMAL]) {
                push @burned, $lmc->[ANIMAL], $iters;
                $hits = 1 if $lmc->[ANIMAL][SPECIES] == HERO;
            } elsif ($lmc->[MINERAL][SPECIES] == WALL) {
                reduce($lmc) if onein(20);
                return -1;
            }
            # NOTE distance() and $iters give different numbers for diagonals
            return $iters > $weap->[W_RANGE] ? -1 : 0;
        },
        $mcol,
        $mrow,
        $tcol,
        $trow
    );
    return MOVE_OKAY, $cost unless $hits;

    bypair(
        sub {
            my ($ani, $iters) = @_;
            apply_damage($ani, 'plburn', $self, $iters) if coinflip();
        },
        @burned
    );

    my $loc = $mcol . ',' . $mrow;
    print at(map { MAP_DOFF + $_ } $mcol, $mrow), 'X'
      if exists $Visible_Cell{$loc};
    my %seen = ($loc => 1);

    for my $point (@path) {
        my ($col, $row) = $point->@[ PCOL, PROW ];
        my $loc = $col . ',' . $row;
        $seen{$loc} = 1;
        if (exists $Visible_Cell{$loc}) {
            print at(map { MAP_DOFF + $_ } $col, $row), coinflip() ? 'X' : 'x';
            $Violent_Sleep_Of_Reason = 1;
        }
    }

    my @spread;
    with_adjacent(
        $mcol, $mrow,
        sub {
            my $loc = join ',', $_[0]->@[ PCOL, PROW ];
            return if $seen{$loc}++ or !exists $Visible_Cell{$loc} or irand(10) < 8;
            print at(map { MAP_DOFF + $_ } $_[0]->@[ PCOL, PROW ]), 'X'
              if exists $Visible_Cell{$loc};
            push @spread, $_[0];
        }
    );
    if (@spread) {
        my $max = 3;
        $max += 2 if onein(40);
        $max += 3 if onein(250);
        # mostly it just looks impressive
        plasma_annihilator($self, \%seen, \@spread, 1, $max);
    }

    return MOVE_OKAY, $cost;
}

sub update_ghast {
    my ($self) = @_;
    my ($mcol, $mrow) = $self->[LMC][WHERE]->@[ PCOL, PROW ];
    my ($tcol, $trow) = $Animates[HERO][LMC][WHERE]->@[ PCOL, PROW ];
    my $weap = $self->[STASH][WEAPON];

    my ($hits, $cost) = does_hit(distance($mcol, $mrow, $tcol, $trow), $weap);
    return MOVE_OKAY, $cost if $hits == -1;

    # but a gatling gun is often trigger happy ...
    if ($hits == 0) {
        return MOVE_OKAY, $cost if onein(8);
        my @nearby;
        with_adjacent($tcol, $trow, sub { push @nearby, $_[0] });
        ($tcol, $trow) = pick(\@nearby)->@[ PCOL, PROW ];
    }

    my @path;
    linecb(
        sub {
            my ($col, $row) = @_;
            push @path, [ $col, $row ];
            if (defined $LMap[$row][$col][ANIMAL]
                and $LMap[$row][$col][ANIMAL][SPECIES] != HERO) {
                ($tcol, $trow) = ($col, $row) if $hits == 0 and coinflip();
                return -1;
            }
            my $cell = $LMap[$row][$col][MINERAL];
            if ($cell->[SPECIES] == WALL) {
                # they're not trigger happy enough to shoot a wall
                # (moreso that letting the wall be shot would reveal
                # where something is to the player)
                @path = ();
                return -1;
            } elsif ($cell->[SPECIES] == RUBBLE) {
                if (onein(10)) {
                    $hits = 0;
                    return -1;
                }
            }
            return 0;
        },
        $mcol,
        $mrow,
        $tcol,
        $trow
    );
    return MOVE_OKAY, $cost unless @path;

    for my $point (@path) {
        my $loc = join ',', $point->@[ PCOL, PROW ];
        if (exists $Visible_Cell{$loc}) {
            print at(map { MAP_DOFF + $_ } $point->@[ PCOL, PROW ]), '-';
            $Violent_Sleep_Of_Reason = 1;
        }
    }
    my $loc = $tcol . ',' . $trow;
    my $lmc = $LMap[$trow][$tcol];
    if ($hits == 0) {
        my $buddy = $LMap[$trow][$tcol][ANIMAL];
        apply_damage($buddy, 'attackby', $self) if defined $buddy;
    } else {
        apply_damage($Animates[HERO], 'attackby', $self);
        $Violent_Sleep_Of_Reason = 1;
    }

    return MOVE_OKAY, $cost;
}

sub update_mimic {
    my ($self) = @_;
    my ($mcol, $mrow) = $self->[LMC][WHERE]->@[ PCOL, PROW ];
    my ($tcol, $trow) = $Animates[HERO][LMC][WHERE]->@[ PCOL, PROW ];
    my $weap = $self->[STASH][WEAPON];

    my ($hits, $cost) = does_hit(distance($mcol, $mrow, $tcol, $trow), $weap);
    return MOVE_OKAY, $cost if $hits == -1;

    my @nearby;
    if ($hits == 0) {
        # maybe they're taking a break
        return MOVE_OKAY, $cost if onein(10);
        with_adjacent($tcol, $trow, sub { push @nearby, $_[0] });
    }

    # Mortars could, in theory, lob shells over walls but that would
    # allow Mortars to abuse things like ### that the player could
    # not get into.                      #M# so require LOS.
    linecb(
        sub {
            my ($col, $row) = @_;
            my $cell = $LMap[$row][$col][MINERAL];
            if ($cell->[SPECIES] == WALL) {
                $hits = 0;
                return -1;
            }
            return 0;
        },
        $mcol,
        $mrow,
        $tcol,
        $trow
    );
    return MOVE_OKAY, $cost if $hits < 1;

    if (@nearby) {
        log_message('A mortar shell explodes nearby!');
        my ($ncol, $nrow) = pick(\@nearby)->@[ PCOL, PROW ];
        my $lmc   = $LMap[$nrow][$ncol];
        my $buddy = $lmc->[ANIMAL];
        if (defined $buddy) {
            apply_damage($buddy, 'attackby', $self);
        } elsif ($lmc->[SPECIES] == WALL and onein(20)) {
            reduce($lmc);
        }
    } else {
        log_message('A mortar shell strikes you!');
        apply_damage($Animates[HERO], 'attackby', $self);
    }

    $Violent_Sleep_Of_Reason = 1;

    return MOVE_OKAY, $cost;
}

sub update_player {
    my ($self) = @_;
    my ($cost, $ret);

    # pre-move tasks
    sb_update_energy();
    if ($Violent_Sleep_Of_Reason == 1) {
        sleep($Draw_Delay);
        $Violent_Sleep_Of_Reason = 0;
    }
    raycast_fov(1);
    show_top_message();
    log_dim();
    show_status_bar();

    tcflush(STDIN_FILENO, TCIFLUSH);
    while (1) {
        my $key = defined $Sticky ? $Sticky : $RKFN->(\%Key_Commands);
        ($ret, $cost, my $code) = $Key_Commands{$key}->($self);
        pkc_log_code($code) if defined $code;
        last                if $ret != MOVE_FAILED;
    }

    if (defined $Sticky) {
        undef $Sticky if --$Sticky_Max <= 0;
        sleep($Draw_Delay) unless $Sticky eq '.';
    }

    my $hp = $self->[STASH][HITPOINTS];
    if (defined $self->[STASH][SHIELDUP] and $hp < START_HP) {
        my $need  = START_HP - $self->[STASH][HITPOINTS];
        my $offer = between(
            0,
            int($cost / $self->[STASH][SHIELDUP][STASH][GEM_REGEN]),
            $self->[STASH][SHIELDUP][STASH][GEM_VALUE]
        );

        my $heal = between(0, $need, $offer);
        $self->[STASH][SHIELDUP][STASH][GEM_VALUE] -= $heal;
        $hp = $self->[STASH][HITPOINTS] += $heal;
        undef $Sticky if $hp == START_HP;

        if ($self->[STASH][SHIELDUP][STASH][GEM_VALUE] <= 0) {
            pkc_log_code('0113');
            log_message(
                'The ' . $self->[STASH][SHIELDUP][STASH][GEM_NAME] . ' chips and shatters!');
            undef $self->[STASH][SHIELDUP];
            undef $Sticky;
            print display_shieldup();
        }
    }

    $Energy_Spent += $cost;
    $Turn_Count++;
    return $ret, $cost;
}

# when player is in range try to shoot them
sub update_troll {
    my ($self) = @_;
    my ($mcol, $mrow) = $self->[LMC][WHERE]->@[ PCOL, PROW ];
    my ($tcol, $trow) = $Animates[HERO][LMC][WHERE]->@[ PCOL, PROW ];
    my $weap = $self->[STASH][WEAPON];

    my ($hits, $cost) = does_hit(distance($mcol, $mrow, $tcol, $trow), $weap);
    return MOVE_OKAY, $cost if $hits == -1;

    my @path;
    my $property_damage = 0;
    walkcb(
        sub {
            my ($col, $row, $iters) = @_;
            push @path, [ $col, $row ];
            if ($iters > $weap->[W_RANGE]) {
                ($tcol, $trow) = ($col, $row) if $hits == 0;
                return -1;
            }
            if (defined $LMap[$row][$col][ANIMAL]
                and $LMap[$row][$col][ANIMAL][SPECIES] != HERO) {
                ($tcol, $trow) = ($col, $row) if $hits == 0;
                return -1;
            }
            my $cell = $LMap[$row][$col][MINERAL];
            if ($cell->[SPECIES] == WALL) {
                $hits = 0;
                if (onein(4)) {
                    ($tcol, $trow) = ($col, $row);
                    $property_damage = 1;
                } else {
                    # wall not getting blow'd up, do not (maybe) reveal
                    # to player that something is trying to do so
                    @path = ();
                }
                return -1;
            } elsif ($cell->[SPECIES] == RUBBLE) {
                # similar FOV problem as for player, see raycast. also
                # should mean that rubble is good cover for the hero
                if (onein(20)) {
                    $hits = 0;
                    ($tcol, $trow) = ($col, $row);
                    $property_damage = 1;
                    return -1;
                }
            }
            return 0;
        },
        $mcol,
        $mrow,
        $tcol,
        $trow
    );
    return MOVE_OKAY, $cost unless @path;

    for my $point (@path) {
        my $loc = join ',', $point->@[ PCOL, PROW ];
        if (exists $Visible_Cell{$loc}) {
            print at(map { MAP_DOFF + $_ } $point->@[ PCOL, PROW ]), '-';
            $Violent_Sleep_Of_Reason = 1;
        }
    }
    my $loc = $tcol . ',' . $trow;
    my $lmc = $LMap[$trow][$tcol];
    if ($property_damage) {
        reduce($lmc);
    } else {
        if ($hits == 0) {
            my $buddy = $LMap[$trow][$tcol][ANIMAL];
            apply_damage($buddy, 'attackby', $self) if defined $buddy;
        } else {
            apply_damage($Animates[HERO], 'attackby', $self);
            $Violent_Sleep_Of_Reason = 1;
        }
    }

    return MOVE_OKAY, $cost;
}

# like shooter but can only fire across totally open ground. advanced
# targetting arrays prevent friendly fire and property damage
sub update_stalker {
    my ($self) = @_;
    my ($mcol, $mrow) = $self->[LMC][WHERE]->@[ PCOL, PROW ];
    my ($tcol, $trow) = $Animates[HERO][LMC][WHERE]->@[ PCOL, PROW ];
    my $weap = $self->[STASH][WEAPON];

    my ($hits, $cost) = does_hit(distance($mcol, $mrow, $tcol, $trow), $weap);
    return MOVE_OKAY, $cost if $hits < 1;

    my @path;
    linecb(
        sub {
            my ($col, $row) = @_;
            if ($col == $tcol and $row == $trow) {    # gotcha
                push @path, [ $col, $row ];
                return 0;
            }
            # stalker needs a really clear shot (to offset for
            # their range)
            my $cell = $LMap[$row][$col][MINERAL];
            if (   defined $LMap[$row][$col][ANIMAL]
                or $cell->[SPECIES] == WALL
                or $cell->[SPECIES] == RUBBLE
                or ($cell->[SPECIES] == ACID and onein(3))) {
                $hits = 0;
                return -1;
            }
            push @path, [ $col, $row ];
        },
        $mcol,
        $mrow,
        $tcol,
        $trow
    );
    return MOVE_OKAY, $cost if $hits < 1 or !@path;

    for my $point (@path) {
        my $loc = join ',', $point->@[ PCOL, PROW ];
        print at(map { MAP_DOFF + $_ } $point->@[ PCOL, PROW ]), '='
          if exists $Visible_Cell{$loc};
    }
    apply_damage($Animates[HERO], 'attackby', $self);

    $Violent_Sleep_Of_Reason = 1;

    return MOVE_OKAY, $weap->[W_COST];
}

sub use_item {
    my ($loot, $i, $stash) = @_;
    if (!($loot->[$i][SPECIES] == GEM or $loot->[$i][SPECIES] == AMULET)) {
        pkc_log_code('0111');
        return 0;
    }
    if (defined $stash->[SHIELDUP]) {
        ($stash->[SHIELDUP], $loot->[$i]) = ($loot->[$i], $stash->[SHIELDUP]);
    } else {
        $stash->[SHIELDUP] = splice $loot->@*, $i, 1;
    }
    return 1;
}



( run in 2.464 seconds using v1.01-cache-2.11-cpan-5837b0d9d2c )