using System; using System.Collections.Generic; namespace UPWG.Core { public enum WrestlerPosition { InRing, OnRopes, OutsideRing, } public enum MomentumLevel { None = 0, Low = 1, Medium = 2, High = 3, } public enum HitResult { Miss, Hit, Critical, } public class MoveResult { public HitResult Result; // what did this do to the recipient? public uint RecipientDamage; public int RecipientMomentumDelta; public BodyParts RecipientRestrained; // and what did this do to us? public uint SelfDamage; public int SelfMomentumDelta; public int SelfPopularityDelta; public MoveResult( HitResult result, uint recipientDamage, int recipientMomentumDelta = 0, BodyParts restrained = 0, uint selfDamage = 0, int selfMomentumDelta = 0, int selfPopularityDelta = 0 ) { Result = result; RecipientDamage = recipientDamage; RecipientMomentumDelta = recipientMomentumDelta; RecipientRestrained = restrained; SelfDamage = selfDamage; SelfMomentumDelta = selfMomentumDelta; SelfPopularityDelta = selfPopularityDelta; } } interface IGoal { public void ProcessMove(Move move, MoveResult result); public bool IsComplete(); } class MaxMomentumGoal : IGoal { public int GoalMomentum { get; } private int _maxMomentum; private int _curMomentum; MaxMomentumGoal(int goalMomentum) { GoalMomentum = goalMomentum; } public void ProcessMove(Move move, MoveResult result) { _curMomentum += result.SelfMomentumDelta; if (_curMomentum > _maxMomentum) _maxMomentum = _curMomentum; } public bool IsComplete() { return _maxMomentum >= GoalMomentum; } } public enum MatchStatus { InProgress, Pinfall, Submission, CountOut, Disqualification, } public class MoveOptions { // this class represents the available moves public string Strike { get; set; } public string Grapple { get; set; } public string Other { get; set; } public string Special { get; set; } } public class Battle { private static Random _random = new Random(); public enum ParticipantRole { LeftSingle, RightSingle, LeftTag1, LeftTag2, RightTag1, RightTag2, } public struct Participant { public Wrestler Wrestler; public WrestlerPosition Position; public BodyParts Restrained; public BodyParts Injured; public MomentumLevel Momentum; public uint MaxFatigue; public uint CurFatigue; public Participant(Wrestler wrestler) { Wrestler = wrestler; Position = WrestlerPosition.InRing; Restrained = 0; Injured = 0; Momentum = MomentumLevel.None; CurFatigue = MaxFatigue = wrestler.Fortitude * 10; } } private uint _turnClock; private ParticipantRole _playerRole; public Dictionary Participants { get; } public MatchStatus Status { get; private set; } public ParticipantRole[] UpcomingTurns { get; set; } public MoveOptions CurrentMoveOptions { get; private set; } private void InitializeState() { _turnClock = 1; Status = MatchStatus.InProgress; UpcomingTurns = new ParticipantRole[5]; CalcUpcomingTurns(); CurrentMoveOptions = new MoveOptions(); CalcPlayerMoveOptions(); } public Battle(Wrestler leftWrestler, Wrestler rightWrestler) { Participants = new Dictionary { [ParticipantRole.LeftSingle] = new Participant(leftWrestler), [ParticipantRole.RightSingle] = new Participant(rightWrestler) }; _playerRole = ParticipantRole.LeftSingle; InitializeState(); } public Battle(Wrestler leftOne, Wrestler leftTwo, Wrestler rightOne, Wrestler rightTwo) { Participants = new Dictionary { [ParticipantRole.LeftTag1] = new Participant(leftOne), [ParticipantRole.LeftTag2] = new Participant(leftTwo), [ParticipantRole.RightTag1] = new Participant(rightOne), [ParticipantRole.RightTag2] = new Participant(rightTwo) }; _playerRole = ParticipantRole.LeftTag1; InitializeState(); } // Player Interaction public void CalcPlayerMoveOptions() { // TODO: revisit for other match types var player = Participants[_playerRole]; var target = Participants[ParticipantRole.RightSingle]; // TODO: revisit for other move types CurrentMoveOptions.Strike = GetBestAvailableMove(player, MoveType.Strike, target); CurrentMoveOptions.Grapple = GetBestAvailableMove(player, MoveType.Aerial, target); CurrentMoveOptions.Special = "Pin"; CurrentMoveOptions.Other = "Enzugiri **"; } public MoveResult ApplyMove(string moveName) { var move = MoveDatabase.Lookup(moveName); return ApplyMove(_playerRole, move, ParticipantRole.RightSingle); } public bool IsPlayerTurn() { return UpcomingTurns[0] == _playerRole; } public void EndTurn() { _turnClock += 1; } // CPU Control public void GetCpuMove(out string move, out ParticipantRole target) { move = "Chop 1"; target = ParticipantRole.LeftSingle; } // Internals private MoveResult ApplyMove(ParticipantRole performerPos, Move move, ParticipantRole recipientPos) { var performer = Participants[performerPos]; var recipient = Participants[recipientPos]; // can this move be done? if (!MoveIsValid(performer, move, recipient)) throw new InvalidMoveException(); var hitResult = CheckForHit(performer, move, recipient); // if move misses, no damage if (hitResult == HitResult.Miss) return new MoveResult(hitResult, 0); uint damage = 0; uint selfDamage = 0; switch (move.Type) { case MoveType.Strike: damage = (uint) (move.BaseOffense * performer.Wrestler.DamageMult); break; case MoveType.Aerial: damage = (uint) (move.BaseOffense * performer.Wrestler.DamageMult); selfDamage = 5; break; case MoveType.Hold: break; case MoveType.Throw: damage = (uint) (move.BaseOffense * performer.Wrestler.DamageMult); break; case MoveType.Pin: break; } // TODO: how does momentum/popularity work? return new MoveResult(hitResult, damage, 0, 0, selfDamage, 0, 0); } private void CalcUpcomingTurns() { for (var i = 0; i < UpcomingTurns.Length; i++) { // TODO: use speed to determine turn order if ((_turnClock + i) % 2 == 1) { UpcomingTurns[i] = ParticipantRole.LeftSingle; } else { UpcomingTurns[i] = ParticipantRole.RightSingle; } } } private static readonly double[] HitMod = {0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4}; private static readonly double[] EvadeMod = {-0.1, -0.1, -0.05, -0.05, 0, 0.05, 0.05, 0.1, 0.1, 0.15}; private static HitResult CheckForHit(Participant performer, Move move, Participant recipient) { var critChance = 0.05; uint attackStat = 0; switch (move.Type) { case MoveType.Strike: attackStat = performer.Wrestler.Strength; break; case MoveType.Aerial: attackStat = performer.Wrestler.Agility; break; case MoveType.Pin: attackStat = performer.Wrestler.Strength > performer.Wrestler.Agility ? performer.Wrestler.Strength : performer.Wrestler.Agility; break; case MoveType.Hold: attackStat = performer.Wrestler.Grappling; break; case MoveType.Throw: attackStat = performer.Wrestler.Grappling; break; } var hitChance = move.BaseChance * HitMod[attackStat] - EvadeMod[recipient.Wrestler.Agility]; var hitRoll = _random.NextDouble(); if (hitRoll < critChance) return HitResult.Critical; else if (hitRoll <= hitChance) return HitResult.Hit; else return HitResult.Miss; } private static bool MoveIsValid(Participant performer, Move move, Participant recipient) { // TODO: check position of performer and recipient // is there an issue affecting the required body part var requiredPartsRestrained = performer.Restrained & move.PartsUtilized; if (requiredPartsRestrained != 0) return false; // does wrestler have momentum? if (performer.Momentum < (MomentumLevel) (move.Rank - 1)) return false; return true; } private static string GetBestAvailableMove(Participant participant, MoveType type, Participant recipient) { List best = new(); var bestRank = -1; foreach (var (moveName, expertise) in participant.Wrestler.MoveSet) { var move = MoveDatabase.Lookup(moveName); // accumulate list of best valid moves of the type if (move.Type == type && MoveIsValid(participant, move, recipient)) { // for now just use rank, eventually this may be dynamic if (move.Rank > bestRank) { bestRank = (int) move.Rank; best.Clear(); best.Add(move); } else if (move.Rank == bestRank) { best.Add(move); } } } return best[_random.Next(best.Count)].Name; } } }