pankration.py 14.6 KB
import random
import time
import pickle

FIND_PERCENTAGE = 100
SKILL_PERCENT = 80

class HuntResponse:
    ERROR = -1
    FAILURE = 0
    SUCCESS = 1

    def __init__(self, result, message, monster):
        self.result = result
        self.message = message
        self.monster = monster

exp_to_level = [500, 1250, 2250, 3500, 5000, 6750, 8750, 10950, 13350, 15950, 18750, 21750, 24950, 28350, 31950, 35750, 39750, 43950, 48350, 52950, 57750, 62750, 67850, 73050, 78350, 83750, 89250, 94850, 100550, 106350, 112250, 118250, 124350, 130550, 136850, 143250, 149750, 156350, 163050, 169850, 176750, 183750, 190850, 198050, 205350, 212750, 220250, 227850, 235550]

class Jobs:
    WAR = 1
    MNK = 2
    BLM = 3
    WHM = 4
    THF = 5
    RDM = 6
    PLD = 7
    DRK = 8
    BST = 9
    BRD = 10
    RNG = 11
    SAM = 12
    NIN = 13
    DRG = 14
    SMN = 15
    BLU = 16
    COR = 17
    PUP = 18
    DNC = 19
    SCH = 20
    GEO = 21
    RUN = 22

TemperamentPosture = {
    4: {"name": "Very Agressive", "message": "Show no mercy!", "value": 4},
    3: {"name": "Somewhat Agressive", "message": "Give em' a little more bite!", "value": 3},
    2: {"name": "Somewhat Defensive", "message": "Back off a bit!", "value": 2},
    1: {"name": "Very Defensive", "message": "Guard! Block! Parry! Hold!", "value": 1}
}

TemperamentAttitude = {
    4: {"name": "Very Wild", "message": "Don't think, kill!", "value": 4},
    3: {"name": "Somewhat Wild", "message": "Less thinking, more striking!", "value": 3},
    2: {"name": "Somewhat Tame", "message": "Watch your opponent, then attack!", "value": 2},
    1: {"name": "Very Tame", "message": "Think, and think again!", "value": 1}
}

PhysicalDamageTypes = {
    'blunt': ['Hand-to-Hand', 'Club', 'Staff', 'Harlequin Frame', 'Stormwaker Frame', 'Sharpshot Frame'],
    'slashing': ['Axe', 'Great Axe', 'Great Sword', 'Sword', 'Scythe', 'Katana', 'Great Katana', 'Valoredge Frame'],
    'piercing': ['Dagger', 'Polearm', 'Archery', 'Marksmanship', 'Shuriken', 'Boomerang']
}

FeralSkills = {
    'Airy Shield': {'fp_cost': 0, 'type': 'Enhancing', 'sub_type': 'arrow shield', 'shadows': 'ignore', 'range': None, 'aoe': False, 'spell': 'arrow shield'},
    'Binding Wave': {'type': 'Enfeebling', 'shadows': 'ignore', 'range': 15, 'aoe': True, 'spell': 'bind'},
    'Dire Straight': {'type': 'Physical', 'shadows': 'wipe', 'range': None, 'aoe': False},
    'Dismemberment': {'type': 'Piercing', 'shadows': 'absorb', 'range': None, 'aoe': False}, # causes the monster to lose a body part
    'Earthshatter': {'type': 'Piercing', 'shadows': 'wipe', 'range': None, 'aoe': True},
    'Eyes On Me': {'type': 'Magical', 'sub_type': 'Dark', 'mp_cost': 112, 'shadows': 'absorb', 'range': 13, 'aoe': False},
    'Hypnosis': {'type': 'Enfeebling', 'sub_type': 'sleep', 'shadows': 'ignore', 'range': 'gaze', 'aoe': True},
    'Magic Barrier': {'type': 'Magical', 'sub_type': 'Dark', 'mp_cost': 29, 'shadows': 'absorb', 'range': None, 'aoe': True},
    'Sinker Drill': {'type': 'Physical', 'sub_type': 'Piercing', 'shadows': 'absorb', 'range': None, 'aoe': False},
}
Families = {
    'acrolith': {
        'base_fp': 50,
        'fp_per_level': 0.1,
        'max_fp': 55,        
        'available_main_job': [Jobs.WAR, Jobs.DRG, Jobs.DRK, Jobs.PLD],
        'available_support_job': [Jobs.WAR, Jobs.DRG, Jobs.DRK, Jobs.PLD],
        'innate_feral_skills': ['Sinker Drill', 'Dire Straight', 'Dismemberment', 'Earthshatter'],
        'type': 'Arcana',
        'strong_vs': ['Dark'],
        'charmable': False,
        'aspir': False,
        'drain': False,
        'temperament_attitude': {
            'initial_value': 4,
            'actions': {
                4: {'use_tp_chance': 100, 'ws': ['Sinker Drill'], 'side_attack_chance': 0, 'range_chance': 0},
                3: {'use_tp_chance': 75, 'ws': ['Eyes On Me'], 'side_attack_chance': 70, 'range_chance': 10},
                2: {'use_tp_chance': 50, 'ws': ['Binding Wave', 'Hypnosis'], 'side_attack_chance': 25, 'range_chance': 70},
                1: {'use_tp_chance': 70, 'ws': ['Binding Wave', 'Hypnosis', 'Magic Barrier', 'Airy Shield'], 'side_attack_chance': 25, 'range_chance': 70}
            }
        },
        'temperament_posture': {
            'initial_value': 4,
            'actions': {
                4: {'use_tp_chance': 100, 'ws': ['Eyes On Me'], 'side_attack_chance': 0, 'range_chance': 0},
                3: {'use_tp_chance': 75, 'ws': ['Eyes On Me'], 'side_attack_chance': 70, 'range_chance': 10},
                2: {'use_tp_chance': 50, 'ws': ['Binding Wave', 'Hypnosis'], 'side_attack_chance': 25, 'range_chance': 70},
                1: {'use_tp_chance': 70, 'ws': ['Magic Barrier', 'Airy Shield'], 'side_attack_chance': 25, 'range_chance': 100}
            }
        }
    },
    'ahriman': {
        'base_fp': 65,
        'fp_per_level': 0.3,
        'max_fp': 80,
        'available_main_job': [Jobs.BLM, Jobs.RDM, Jobs.WAR],
        'available_support_job': [Jobs.BLM, Jobs.RDM, Jobs.WAR],
        'innate_feral_skills': ['Binding Wave', 'Magic Barrier', 'Hypnosis', 'Eyes On Me', 'Airy Shield'],
        'type': 'Demon',
        'traits': ['magic defence bonus +25%'],
        'charmable': False,
        'aspir': True,
        'drain': True,
        'temperament_attitude': {
            'initial_value': 4,
            'actions': {
                4: {'use_tp_chance': 100, 'ws': ['Eyes On Me'], 'side_attack_chance': 0, 'range_chance': 0},
                3: {'use_tp_chance': 75, 'ws': ['Eyes On Me'], 'side_attack_chance': 70, 'range_chance': 10},
                2: {'use_tp_chance': 50, 'ws': ['Binding Wave', 'Hypnosis'], 'side_attack_chance': 25, 'range_chance': 70},
                1: {'use_tp_chance': 70, 'ws': ['Binding Wave', 'Hypnosis', 'Magic Barrier', 'Airy Shield'], 'side_attack_chance': 25, 'range_chance': 70}
            }
        },
        'temperament_posture': {
            'initial_value': 4,
            'actions': {
                4: {'use_tp_chance': 100, 'ws': ['Eyes On Me'], 'side_attack_chance': 0, 'range_chance': 0},
                3: {'use_tp_chance': 75, 'ws': ['Eyes On Me'], 'side_attack_chance': 70, 'range_chance': 10},
                2: {'use_tp_chance': 50, 'ws': ['Binding Wave', 'Hypnosis'], 'side_attack_chance': 25, 'range_chance': 70},
                1: {'use_tp_chance': 70, 'ws': ['Magic Barrier', 'Airy Shield'], 'side_attack_chance': 25, 'range_chance': 100}
            }
        }
    }
}

Monsters = {
    'Mechanical Menace': {
        'initial_level': 9,
        'family': 'acrolith',
        'zone': 'Abyssea - Uleguerand'
    },
    'Floating Eye': {
        'initial_level': 3,
        'family': 'ahriman',
        'zone': 'Ranguemont Pass'
    }
}


class Pankration:
    def __init__(self):
        pass

    def start_battle(self, monster1, monster2, battle_type):
        battle = Battle(monster1, monster2, battle_type)
        battle.start()
        return battle

    def list_zones(self):
        zone_list = []
        for monster in Monsters.itervalues():
            if monster['zone'] not in zone_list:
                zone_list.append(monster['zone'])
        return zone_list

    def get_monsters(self, zone):
        monster_list = []
        for monster_name, monster in Monsters.iteritems():
            if monster['zone'] == zone:
                monster_list.append(monster_name)
        return monster_list

    def hunt_monster(self, zone):
        if random.randint(1, 100) < FIND_PERCENTAGE:
            monster_data = Monsters[random.choice(self.get_monsters(zone))]
            print(monster_data)
            family_name = monster_data['family']
            level = monster_data['initial_level']
            family = Families[family_name]
            main_job = random.choice(family['available_main_job'])
            support_job = random.choice(family['available_support_job'])
            feral_skills = []
            skills = random.sample(family['innate_feral_skills'], 3)
            if random.randint(1, 100) < SKILL_PERCENT:
                feral_skills.append(skills[0])
                if random.randint(1, 100) < SKILL_PERCENT:
                    feral_skills.append(skills[1])
                    if random.randint(1, 100) < SKILL_PERCENT:
                        feral_skills.append(skills[2])
            dicipline_level = 1

            monster = Monster(family, level, main_job, support_job, feral_skills,
                              [], dicipline_level)
            return HuntResponse(HuntResponse.SUCCESS, "You captured the monster!",
                                monster)
        else:
            return HuntResponse(HuntResponse.FAILURE, "You were unable to capture a monster's soul.",
                                None)

    def get_action():
        return ""


class Monster:
    def __init__(self, family, level, main_job, support_job, innate_feral_skills,
                 equipped_feral_skills, dicipline_level):
        self.family = family
        self.level = level
        self.main_job = main_job
        self.support_job = support_job
        self.innate_feral_skills = innate_feral_skills
        self.equipped_feral_skills = equipped_feral_skills
        self.discipline_level = dicipline_level
        self.temperament_posture = family['temperament_posture']['initial_value']
        self.temperament_attitude = family['temperament_attitude']['initial_value']
        self.pre_fight_command = {"temperament_posture": None, "temperament_attitude": None}
        self.exp = exp_to_level[level] + 1
        # TODO: Setup something more interesting for each monster. 
        self.weapon_base_damage = 43
        self.ammo_damage = 0

    def __str__(self):
        return "Family: {}\nLevel: {}\nMain Job: {}\nSupport Job: {}\nInnate Feral Skills: {}\nEquipped Feral Skills: {}\nDicipline Level: {}\nTemperament...".format(self.family, self.level, self.main_job, self.support_job, self.innate_feral_skills, self.equipped_feral_skills, self.discipline_level)

    def get_fp(self):
        return int(self.level / self.family['fp_per_level'])

    def add_xp(self, exp_to_add):
        self.exp += exp_to_add
        for i in range(self.level, 50):
            if self.exp > exp_to_level[i]:
                if i > self.level:
                    # We leveled up!
                    print("Start level: {} New Level: {}".format(self.level, i))
                else:
                    print("Just another brick in the wall...")
            else:
                break
        # calc level based on xp
        # stop at 50
        pass

    def get_current_posture(self):
        return TemperamentPosture(self.temperament_posture)

    def get_current_attitude(self):
        return TemperamentAttitude(self.temperament_attitude)

    def get_dicipline_level(self):
        if self.dicipline_level < 2:
            return "Defiant"
        elif self.dicipline_level < 5:
            return "Disrespectful"
        elif self.dicipline_level < 7:
            return "Disobedient"
        else:
            return "Obedient"

    # Calculate the base damage against the provided defense
    # attack_type can be 'ranged' or 'melee'
    def get_physical_base_damage(self, defense, attack_type, level_difference):
        # Calculate the attack/defense ratio
        bd = float(self.weapon_base_damage)
        if attack_type == 'ranged':
            bd += self.ammo_damage
        # NOTE: We are not using fStr1 or fStr2 since we are not keeping str / vit as data
        if defense == 0:
            ratio = 99
        else:
            ratio = bd / float(defense)
        if attack_type == 'ranged':
            cap = 3.0
        else:
            cap = 2.25
        if ratio > cap:
            ratio = cap
        # Get the level correction ratio
        if attack_type == 'ranged':
            cRatio = ratio - 0.025 * level_difference
        else:
            cRatio = ratio - 0.050 * level_difference
        if cRatio < 0:
            cRatio = 0
        if cRatio > 3.0:
            cRatio = 3.0
        # Calculate the pDif max as the max of our RNG
        if cRatio <= 0.5:
            pDif_max = 1+(10/9)*(cRatio-0.5)
        elif 0.5 <= cRatio <= float(3/4):
            pDif_max = 1
        elif float(3/4) <= cRatio <= cap:
            pDif_max = 1+(10/9)*(cRatio-float(3/4))

        # Calculate the pDif max as the max of our RNG
        if cRatio <= 0.5:
            pDif_min = 1/6
        elif 0.5 <= cRatio <= 1.25:
            pDif_min = 1+(10/9)*(cRatio-1.25)
        elif 1.25 <= cRatio <= 1.5:
            pDif_min = 1
        elif 1.5 <= cRatio <= cap:
            pDif_min = 1+(10/9)*(cRatio-1.5)

        print(pDif_min)
        # Some fuckery since python doesn't let you do a rand between decimals
        pDif_rand = float(random.randint(int(pDif_min*100000), int(pDif_max*100000))) / 100000.0
        
        final_damage = bd * (pDif_rand * cRatio)
        return final_damage

    # This should ONLY be executed at the start of battle.
    def set_strategy(self, temperament_posture, temperament_attitude):        
        distance_from_nature = abs(self.temperament_posture - temperament_posture)
        distance_from_nature += abs(self.temperament_attitude - temperament_attitude)

        if distance_from_nature == 0:
            chance_of_listening = self.discipline_level
        else:
            chance_of_listening = float(float(self.discipline_level) / (float(distance_from_nature)/10))

        chance_of_listening = abs(float((float(chance_of_listening)-1)/(20-1)))

        if random.randint(1, 100) > chance_of_listening * 100:
            print("DO ACTION:")
            # adjust dicipline_level
            # move temperament
        else:
            #continue action
            #if severe request then reduce dicipline_level
            pass


class Battle:
    def __init__(self, monster1, monster2, battle_type):
        self.monster1 = monster1
        self.monster2 = monster2
        self.battle_type = battle_type

    def perform_attack():
        base_damage = get_physical_base_damage(monster2.defense, 'melee', 0)


class Arena:
    pass

#print Families

p = Pankration()
print("Zones: \n\n{}".format('\n'.join(p.list_zones())))
hunt_zone = p.list_zones()[0]
print("Hunting for monster in zone: {}\n".format(hunt_zone))
hunt_response = p.hunt_monster(hunt_zone)
print("hunt_response")
print(str(hunt_response.result))
time.sleep(0.5)
if hunt_response.result == HuntResponse.SUCCESS:
    print(hunt_response.message)
    monster = hunt_response.monster
    print("The Soul Plate Shows: \n\n{}".format(monster))
    print(monster.set_strategy(4, 3))
    print(monster.set_strategy(1, 1))
    print(monster.set_strategy(1, 2))
    monster.add_xp(2900)
    phy_damage = monster.get_physical_base_damage(100, 'melee', -15)
    print("Phys Attack: {}".format(phy_damage))
else:
    print(hunt_response.message)