612192b6 by Barry

Most of the magic system is now in place.

1 parent 03403a56
......@@ -67,6 +67,7 @@ def db_buy_ticket(member_id, amount):
return True, int(credits[1]) + int(amount)
except Exception as e:
log(e)
def db_update_credit(member_id, amount):
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
......@@ -168,7 +169,6 @@ def db_get_fortune():
try:
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
# TODO: Move this shit to data
return c.execute("SELECT fortune FROM fortunes ORDER BY RANDOM() LIMIT 1;").fetchone()[0]
finally:
conn.close()
......@@ -192,7 +192,6 @@ def db_get_fortune():
try:
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
# TODO: Move this shit to data
return c.execute("SELECT fortune FROM fortunes ORDER BY RANDOM() LIMIT 1;").fetchone()[0]
finally:
conn.close()
......
No preview for this file type
......@@ -27,7 +27,7 @@ import wolframalpha
import sqlite3
from blackjack import Blackjack
import data
from pankration import Pankration, HuntResponse, Jobs, Action, MissAction, AttackAction, DefeatAction, TemperamentPosture, TemperamentAttitude
from pankration import Pankration, HuntResponse, Jobs, Action, MagicResistAction, MissAction, AttackAction, DefeatAction, TemperamentPosture, TemperamentAttitude
VERSION = 2.3
......@@ -1203,7 +1203,7 @@ def do_hunt_monster(client, message_parts, message):
if not result:
send_message(client, message.author, error_message)
return
send_message(client, message.channel, 'Soul Plate purchased for {} credits\n Hunting in {}'.format(cost, zone))
send_message(client, message.channel, '{} Soul Plate purchased for {} credits\n Hunting in {}'.format(message.author.name, cost, zone))
time.sleep(3)
hunt_response = p.hunt_monster(' '.join(message_parts))
str_out = hunt_response.message + "\n\n"
......@@ -1212,7 +1212,7 @@ def do_hunt_monster(client, message_parts, message):
member = data.db_get_member(message.author.id)
data.db_add_soul_plate(member['member_id'], soul_plate)
str_out += str(soul_plate.get_soul_plate_description())
send_message(client, message.channel, str_out)
send_message(client, message.channel, "{} {}".format(message.author.name, str_out))
def do_list_zones(client, message_parts, message):
......@@ -1271,12 +1271,20 @@ def check_arena():
time.sleep(4)
out_str = ""
for action in actions:
# TODO: Decide if I should merge magicresistaction with miss action
if isinstance(action, MagicResistAction):
out_str += "{} {} {} while casting {}.\n".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), action.spell)
if isinstance(action, MissAction):
out_str += "{} {} {}.\n".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name())
if action.spell:
out_str += "{} {} {} while casting {}.\n".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), action.spell)
else:
out_str += "{} {} {}.\n".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name())
if isinstance(action, AttackAction):
out_str += "{} {} {} for {}.\n".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), int(action.damage))
if action.spell:
out_str += "{} {} {} against {} for {}.\n".format(action.attacker.get_monster_name(), action.message, action.spell, action.target.get_monster_name(), int(action.damage))
else:
out_str += "{} {} {} for {}.\n".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), int(action.damage))
if isinstance(action, DefeatAction):
out_str += "\n\n**{}** {}. {} gains {} xp.\n\n".format(action.target.get_monster_name(), action.message, action.attacker.get_monster_name(), action.xp)
fighting = False
if action.attacker == monster:
......@@ -1294,7 +1302,8 @@ def check_arena():
monster2.wins += 1
monster.losses += 1
break
out_str += "\n{} {}% - {} {}%\n".format(monster.get_monster_name(), monster.get_hp_percent(), monster2.get_monster_name(), monster2.get_hp_percent())
if fighting:
out_str += "\n{} {}% - {} {}%\n".format(monster.get_monster_name(), monster.get_hp_percent(), monster2.get_monster_name(), monster2.get_hp_percent())
log(out_str)
send_message(client, arena_channel, byteify(out_str))
# Heal the monsters before they are returned to the player inventory
......
......@@ -87,29 +87,30 @@ class Jobs:
members = [attr for attr in dir(Jobs()) if not callable(attr) and not attr.startswith("__") and not attr.startswith("get_job_name")]
return members[job-1]
evasion_by_job = {
Jobs.BLM: {'base_eva': 4, 'eva_per_level': 2.48},
Jobs.BLU: {'base_eva': 5, 'eva_per_level': 2.78},
Jobs.BRD: {'base_eva': 4, 'eva_per_level': 2.66},
Jobs.BST: {'base_eva': 5, 'eva_per_level': 2.78},
Jobs.COR: {'base_eva': 4, 'eva_per_level': 2.66},
Jobs.DNC: {'base_eva': 5, 'eva_per_level': 2.88},
Jobs.DRG: {'base_eva': 5, 'eva_per_level': 2.88},
Jobs.DRK: {'base_eva': 5, 'eva_per_level': 2.78},
Jobs.GEO: {'base_eva': 4, 'eva_per_level': 2.66},
Jobs.MNK: {'base_eva': 5, 'eva_per_level': 2.88},
Jobs.NIN: {'base_eva': 6, 'eva_per_level': 3},
Jobs.PLD: {'base_eva': 5, 'eva_per_level': 2.78},
Jobs.PUP: {'base_eva': 5, 'eva_per_level': 2.88},
Jobs.RDM: {'base_eva': 4, 'eva_per_level': 2.66},
Jobs.RNG: {'base_eva': 4, 'eva_per_level': 2.48},
Jobs.RUN: {'base_eva': 5, 'eva_per_level': 2.88},
Jobs.SAM: {'base_eva': 5, 'eva_per_level': 2.88},
Jobs.SCH: {'base_eva': 4, 'eva_per_level': 2.48},
Jobs.SMN: {'base_eva': 4, 'eva_per_level': 2.48},
Jobs.THF: {'base_eva': 6, 'eva_per_level': 3},
Jobs.WAR: {'base_eva': 5, 'eva_per_level': 2.78},
Jobs.WHM: {'base_eva': 4, 'eva_per_level': 2.48},
# MP Per level is actually the amount added to the multiplier per level for the mp base for that monster.
adjustments_by_job = {
Jobs.BLM: {'base_eva': 4, 'eva_per_level': 2.48, 'mp_per_level': 0.3},
Jobs.BLU: {'base_eva': 5, 'eva_per_level': 2.78, 'mp_per_level': 0.15},
Jobs.BRD: {'base_eva': 4, 'eva_per_level': 2.66, 'mp_per_level': 0},
Jobs.BST: {'base_eva': 5, 'eva_per_level': 2.78, 'mp_per_level': 0},
Jobs.COR: {'base_eva': 4, 'eva_per_level': 2.66, 'mp_per_level': 0},
Jobs.DNC: {'base_eva': 5, 'eva_per_level': 2.88, 'mp_per_level': 0},
Jobs.DRG: {'base_eva': 5, 'eva_per_level': 2.88, 'mp_per_level': 0},
Jobs.DRK: {'base_eva': 5, 'eva_per_level': 2.78, 'mp_per_level': 0.1},
Jobs.GEO: {'base_eva': 4, 'eva_per_level': 2.66, 'mp_per_level': 0},
Jobs.MNK: {'base_eva': 5, 'eva_per_level': 2.88, 'mp_per_level': 0},
Jobs.NIN: {'base_eva': 6, 'eva_per_level': 3, 'mp_per_level': 0},
Jobs.PLD: {'base_eva': 5, 'eva_per_level': 2.78, 'mp_per_level': 0.1},
Jobs.PUP: {'base_eva': 5, 'eva_per_level': 2.88, 'mp_per_level': 0},
Jobs.RDM: {'base_eva': 4, 'eva_per_level': 2.66, 'mp_per_level': 0.15},
Jobs.RNG: {'base_eva': 4, 'eva_per_level': 2.48, 'mp_per_level': 0},
Jobs.RUN: {'base_eva': 5, 'eva_per_level': 2.88, 'mp_per_level': 0.1},
Jobs.SAM: {'base_eva': 5, 'eva_per_level': 2.88, 'mp_per_level': 0},
Jobs.SCH: {'base_eva': 4, 'eva_per_level': 2.48, 'mp_per_level': 0.15},
Jobs.SMN: {'base_eva': 4, 'eva_per_level': 2.48, 'mp_per_level': 0.4},
Jobs.THF: {'base_eva': 6, 'eva_per_level': 3, 'mp_per_level': 0},
Jobs.WAR: {'base_eva': 5, 'eva_per_level': 2.78, 'mp_per_level': 0},
Jobs.WHM: {'base_eva': 4, 'eva_per_level': 2.48, 'mp_per_level': 0.2},
}
TemperamentPosture = {
......@@ -143,6 +144,78 @@ FeralSkills = {
'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},
}
class Spells:
SpellTypes = {
't1': {'multiplier': 1.0, 'earth': 10, 'water': 16, 'wind': 25, 'fire': 35, 'ice': 46, 'thunder': 60, 'dark': 0, 'light': 14},
't2': {'multiplier': 1.0, 'earth': 78, 'water': 95, 'wind': 113, 'fire': 133, 'ice': 155, 'thunder': 178, 'dark': 0, 'light': 85},
't3': {'multiplier': 1.5, 'earth': 210, 'water': 236, 'wind': 265, 'fire': 295, 'ice': 320, 'thunder': 345, 'dark': 0, 'light': 198},
't4': {'multiplier': 2.0, 'earth': 381, 'water': 410, 'wind': 440, 'fire': 472, 'ice': 506, 'thunder': 541, 'dark': 0, 'light': 0},
't5': {'multiplier': 2.299, 'earth': 626, 'water': 680, 'wind': 738, 'fire': 785, 'ice': 828, 'thunder': 874, 'dark': 963, 'light': 0},
't1-ga': {'multiplier': 1.0, 'earth': 56, 'water': 74, 'wind': 93, 'fire': 120, 'ice': 145, 'thunder': 172, 'dark': 0, 'light': 50},
't2-ga-a': {'multiplier': 1.0, 'earth': 201, 'water': 232, 'wind': 266, 'fire': 312, 'ice': 350, 'thunder': 392, 'dark': 0, 'light': 180},
't2-ga-b': {'multiplier': 1.5, 'earth': 201, 'water': 232, 'wind': 266, 'fire': 312, 'ice': 350, 'thunder': 392, 'dark': 0, 'light': 180},
't3-ga': {'multiplier': 1.5, 'earth': 434, 'water': 480, 'wind': 527, 'fire': 589, 'ice': 642, 'thunder': 697, 'dark': 0, 'light': 0},
'ja': {'multiplier': 1.0, 'earth': 719, 'water': 782, 'wind': 844, 'fire': 902, 'ice': 953, 'thunder': 1004, 'dark': 0, 'light': 0},
't1-ra': {'multiplier': 1.0, 'earth': 128, 'water': 153, 'wind': 179, 'fire': 216, 'ice': 247, 'thunder': 282, 'dark': 0, 'light': 0},
't2-ra-a': {'multiplier': 1.0, 'earth': 317, 'water': 356, 'wind': 396, 'fire': 450, 'ice': 496, 'thunder': 544, 'dark': 0, 'light': 0},
't2-ra-b': {'multiplier': 1.5, 'earth': 317, 'water': 356, 'wind': 396, 'fire': 450, 'ice': 496, 'thunder': 544, 'dark': 0, 'light': 0},
't1-am': {'multiplier': 2.0, 'earth': 577, 'water': 630, 'wind': 552, 'fire': 657, 'ice': 526, 'thunder': 603, 'dark': 939, 'light': 125},
't2-am': {'multiplier': 2.0, 'earth': 710, 'water': 710, 'wind': 710, 'fire': 710, 'ice': 710, 'thunder': 710, 'dark': 0, 'light': 0},
'helix': {'multiplier': 1.0, 'earth': 25, 'water': 25, 'wind': 25, 'fire': 25, 'ice': 25, 'thunder': 25, 'dark': 25, 'light': 25},
'ichi': {'multiplier': 1.0, 'earth': 10, 'water': 10, 'wind': 10, 'fire': 10, 'ice': 10, 'thunder': 10, 'dark': 0, 'light': 0},
'ni': {'multiplier': 1.0, 'earth': 78, 'water': 78, 'wind': 78, 'fire': 78, 'ice': 78, 'thunder': 78, 'dark': 0, 'light': 0},
'san': {'multiplier': 1.5, 'earth': 78, 'water': 78, 'wind': 78, 'fire': 78, 'ice': 78, 'thunder': 78, 'dark': 0, 'light': 0},
}
# TODO: Finish jobs listing for each spell.
SpellList = {
'Stone': {'mp_cost': 4, 'casting_time': 0.5, 'recast_time': 2, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'earth', 'jobs': {Jobs.BLM: 1, Jobs.RDM: 4, Jobs.SCH: 4, Jobs.GEO: 4, Jobs.DRK: 5, Jobs.PUP: 5} },
'Water': {'mp_cost': 5, 'casting_time': 0.5, 'recast_time': 2, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'water', 'jobs': {Jobs.BLM: 5, Jobs.SCH: 8, Jobs.RDM: 9, Jobs.GEO: 9, Jobs.PUP: 10, Jobs.DRK: 11} },
'Aero': {'mp_cost': 6, 'casting_time': 0.5, 'recast_time': 2, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'wind', 'jobs': {Jobs.BLM: 9, Jobs.SCH: 12, Jobs.RDM: 14, Jobs.PUP: 15, Jobs.DRK: 17} },
'Fire': {'mp_cost': 7, 'casting_time': 0.5, 'recast_time': 2, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'fire', 'jobs': {Jobs.BLM: 13, Jobs.SCH: 16, Jobs.RDM: 19, Jobs.GEO: 19, Jobs.PUP: 20, Jobs.DRK: 23} },
'Blizzard': {'mp_cost': 8, 'casting_time': 0.5, 'recast_time': 2, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'ice', 'jobs': {Jobs.BLM: 17, Jobs.SCH: 20, Jobs.RDM: 24, Jobs.PUP: 24, Jobs.DRK: 29} },
'Thunder': {'mp_cost': 9, 'casting_time': 0.5, 'recast_time': 2, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'lightning', 'jobs': {Jobs.BLM: 21, Jobs.SCH: 24, Jobs.RDM: 29, Jobs.PUP: 29, Jobs.DRK: 35} },
'Banish': {'mp_cost': 15, 'casting_time': 3, 'recast_time': 14, 'magic_acc_stat': 'mnd', 'spell_type': 't1', 'damage_type': 'light', 'jobs': {Jobs.WHM: 5, Jobs.PLD: 7} },
'Stone II': {'mp_cost': 16, 'casting_time': 1.5, 'recast_time': 6, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'earth', 'jobs': {Jobs.BLM: 26, Jobs.SCH: 30, Jobs.RDM: 35, Jobs.DRK: 42, Jobs.PUP: 35} },
'Water II': {'mp_cost': 19, 'casting_time': 1.5, 'recast_time': 6, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'water', 'jobs': {Jobs.BLM: 30, Jobs.SCH: 34, Jobs.RDM: 40, Jobs.PUP: 40, Jobs.DRK: 48} },
'Aero II': {'mp_cost': 22, 'casting_time': 1.5, 'recast_time': 6, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'wind', 'jobs': {Jobs.BLM: 34, Jobs.SCH: 38, Jobs.RDM: 45, Jobs.PUP: 45, Jobs.DRK: 54} },
'Fire II': {'mp_cost': 26, 'casting_time': 1.5, 'recast_time': 6, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'fire', 'jobs': {Jobs.BLM: 38, Jobs.SCH: 42, Jobs.RDM: 50, Jobs.PUP: 50, Jobs.DRK: 60} },
'Blizzard II': {'mp_cost': 31, 'casting_time': 1.5, 'recast_time': 6, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'ice', 'jobs': {Jobs.BLM: 42, Jobs.SCH: 46, Jobs.RDM: 55, Jobs.PUP: 55, Jobs.DRK: 66} },
'Thunder II': {'mp_cost': 37, 'casting_time': 1.5, 'recast_time': 6, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'lightning', 'jobs': {Jobs.BLM: 46, Jobs.SCH: 51, Jobs.RDM: 60, Jobs.PUP: 60, Jobs.DRK: 72} },
'Banish II': {'mp_cost': 57, 'casting_time': 2.5, 'recast_time': 21, 'magic_acc_stat': 'mnd', 'spell_type': 't1', 'damage_type': 'light', 'jobs': {Jobs.WHM: 30, Jobs.PLD: 34} },
'Stone III': {'mp_cost': 40, 'casting_time': 3, 'recast_time': 15, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'earth', 'jobs': {Jobs.BLM: 51, Jobs.SCH: 54, Jobs.RDM: 65, Jobs.DRK: 76, Jobs.PUP: 65} },
'Water III': {'mp_cost': 46, 'casting_time': 3, 'recast_time': 15, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'water', 'jobs': {Jobs.BLM: 55, Jobs.SCH: 57, Jobs.RDM: 67, Jobs.PUP: 67, Jobs.DRK: 80} },
'Aero III': {'mp_cost': 54, 'casting_time': 3, 'recast_time': 15, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'wind', 'jobs': {Jobs.BLM: 59, Jobs.SCH: 60, Jobs.RDM: 69, Jobs.PUP: 69, Jobs.DRK: 84} },
'Fire III': {'mp_cost': 63, 'casting_time': 3, 'recast_time': 15, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'fire', 'jobs': {Jobs.BLM: 62, Jobs.SCH: 63, Jobs.RDM: 71, Jobs.PUP: 70, Jobs.DRK: 88} },
'Blizzard III': {'mp_cost': 75, 'casting_time': 3, 'recast_time': 15, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'ice', 'jobs': {Jobs.BLM: 64, Jobs.SCH: 66, Jobs.RDM: 73, Jobs.PUP: 71, Jobs.DRK: 92} },
'Thunder III': {'mp_cost': 91, 'casting_time': 3, 'recast_time': 15, 'magic_acc_stat': 'int', 'spell_type': 't1', 'damage_type': 'lightning', 'jobs': {Jobs.BLM: 66, Jobs.SCH: 69, Jobs.RDM: 75, Jobs.PUP: 72, Jobs.DRK: 96} },
'Banish III': {'mp_cost': 96, 'casting_time': 3, 'recast_time': 45, 'magic_acc_stat': 'mnd', 'spell_type': 't1', 'damage_type': 'light', 'jobs': {Jobs.WHM: 65} },
}
def get_spell_list(self, level, job = None, sub_job = None):
available_spells = []
for spell_name, spell in self.SpellList.iteritems():
if job and job in spell['jobs'] and spell['jobs'][job] <= level:
if spell_name not in available_spells:
available_spells.append(spell_name)
if sub_job and sub_job in spell['jobs'] and spell['jobs'][sub_job] <= level / 2:
if spell_name not in available_spells:
available_spells.append(spell_name)
return sorted(available_spells)
def get_spell_data(self, spell):
spell = self.SpellList[spell]
type_data = self.SpellTypes[spell['spell_type']]
spell_data = {
'type': type_data,
'magic_acc_stat': spell['magic_acc_stat'],
'base_damage': type_data[spell['damage_type']],
'multiplier': type_data['multiplier'],
'mp_cost': spell['mp_cost']
}
return spell_data
Families = {
'acrolith': {
'base_fp': 50,
......@@ -151,6 +224,7 @@ Families = {
'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'],
'target_magic_damage_adjustment': 1.00,
'type': 'Arcana',
'strong_vs': ['Dark'],
'charmable': False,
......@@ -168,6 +242,8 @@ Families = {
'mnd_per_level': 0.76,
'base_int': 8,
'int_per_level': 0.76,
'base_chr': 7,
'chr_per_level': 0.76,
'temperament_attitude': {
'initial_value': 4,
'actions': {
......@@ -196,6 +272,7 @@ Families = {
'innate_feral_skills': ['Binding Wave', 'Magic Barrier', 'Hypnosis', 'Eyes On Me', 'Airy Shield'],
'type': 'Demon',
'traits': ['magic defence bonus +25%'],
'target_magic_damage_adjustment': 0.75,
'charmable': False,
'aspir': True,
'drain': True,
......@@ -211,6 +288,8 @@ Families = {
'mnd_per_level': 0.9066,
'base_int': 9,
'int_per_level': 0.9066,
'base_chr': 8,
'chr_per_level': 0.84,
'temperament_attitude': {
'initial_value': 4,
'actions': {
......@@ -237,12 +316,14 @@ Monsters = {
'family': 'acrolith',
'zone': ['abyssea - uleguerand'],
'hp': 20,
'mp': 20,
'weapon_base_damage': 18
},
'Floating Eye': {
'family': 'ahriman',
'zone': ['ranguemont pass'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15,
'image_url': 'http://vignette1.wikia.nocookie.net/ffxi/images/9/94/Floating_Eye.JPG/revision/latest?cb=20070704142439'
},
......@@ -250,6 +331,7 @@ Monsters = {
'family': 'ahriman',
'zone': ['ranguemont pass', 'beaucedine glacier'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15,
'image_url': 'http://vignette3.wikia.nocookie.net/ffxi/images/4/40/Bat_Eye.jpg/revision/latest?cb=20060909124134',
},
......@@ -257,66 +339,77 @@ Monsters = {
'family': 'ahriman',
'zone': ['castle zvahl baileys', 'castle zvahl keep', 'xarcabard'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15,
},
'Morbid Eye': {
'family': 'ahriman',
'zone': ['castle zvahl baileys', 'castle zvahl keep'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Deadly Iris': {
'family': 'ahriman',
'zone': ['castle zvahl keep'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Ahriman': {
'family': 'ahriman',
'zone': ['castle zvahl baileys'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Fachan': {
'family': 'ahriman',
'zone': ['uleguerand range'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Gawper': {
'family': 'ahriman',
'zone': ['beaucedine glacier (s)'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Menacing Eye': {
'family': 'ahriman',
'zone': ['xarcabard (s)'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Ogler': {
'family': 'ahriman',
'zone': ['castle zvahl keep (s)', 'castle zvahl baileys (s)'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Smolenkos': {
'family': 'ahriman',
'zone': ['uleguerand range'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Doom Lens': {
'family': 'ahriman',
'zone': ['castle zvahl keep (s)', 'castle zvahl baileys (s)'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
},
'Scowlenkos': {
'family': 'ahriman',
'zone': ['uleguerand range'],
'hp': 15,
'mp': 24,
'weapon_base_damage': 15
}
......@@ -372,6 +465,7 @@ class Pankration:
monster_name = random.choice(self.get_monsters(zone))
monster_data = Monsters[monster_name]
hp = monster_data['hp']
mp = monster_data['mp']
weapon_base_damage = monster_data['weapon_base_damage']
log("Monster: {} Data: {}".format(monster_name, monster_data))
family_name = monster_data['family']
......@@ -391,7 +485,8 @@ class Pankration:
feral_skills.append(skills[2])
dicipline_level = 1
monster = Monster(monster_name, family_name, family, hp, level, weapon_base_damage, main_job, support_job, feral_skills,
monster = Monster(monster_name, family_name, family, hp, mp, level,
weapon_base_damage, main_job, support_job, feral_skills,
[], dicipline_level)
url = ''
if 'image_url' in monster_data:
......@@ -405,15 +500,26 @@ class Pankration:
def get_action():
return ""
class AttackResponse:
def __init__(self, is_a_hit, damage, is_a_crit, spell, resisted):
self.spell = spell
self.is_a_crit = is_a_crit
self.is_a_hit = is_a_hit
self.resisted = resisted
self.damage = damage
def __str__(self):
return "Spell: {} Resisted: {} Is a Hit: {} Is a Crit: {} Damage: {}".format(self.spell, self.resisted, self.is_a_hit, self.is_a_crit, self.damage)
class Monster:
def __init__(self, monster_type, family_name, family, base_hp, level, weapon_base_damage, main_job, support_job, innate_feral_skills,
equipped_feral_skills, dicipline_level):
def __init__(self, monster_type, family_name, family, base_hp, base_mp, level, weapon_base_damage, main_job,
support_job, innate_feral_skills, equipped_feral_skills, dicipline_level):
self.custom_name = None
self.monster_type = monster_type
self.family_name = family_name
self.family = family
self.base_hp = base_hp
self.base_mp = base_mp
self.level = level
self.main_job = main_job
self.support_job = support_job
......@@ -423,7 +529,8 @@ class Monster:
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
self.target_magic_damage_adjustment = family['target_magic_damage_adjustment']
self.exp = 200 * (level - 1)
# TODO: Setup something more interesting for each monster.
self.weapon_base_damage = weapon_base_damage
self.ammo_damage = 0
......@@ -439,8 +546,11 @@ class Monster:
self.mnd_per_level = family['mnd_per_level']
self.base_int = family['base_int']
self.int_per_level = family['int_per_level']
self.base_chr = family['base_chr']
self.chr_per_level = family['chr_per_level']
self.tp = 0
self.hp = self.get_hp()
self.mp = self.get_mp()
self.wins = 0
self.losses = 0
......@@ -478,20 +588,31 @@ class Monster:
#this is a bit dumb but it's close enough.
return int((self.level * 20) + self.base_hp)
def get_mp(self):
#this is a bit dumb but it's close enough.
main_add_amount = self.base_mp * adjustments_by_job[self.main_job]['mp_per_level']
sub_add_amount = self.base_mp * adjustments_by_job[self.support_job]['mp_per_level']
main_mp = int((self.level * (self.base_mp + main_add_amount)) + self.base_mp)
sub_mp = int((self.level/2) * sub_add_amount)
return main_mp + sub_mp
def get_fp(self):
return int(self.level / self.family['fp_per_level'] + self.family['base_fp'])
def add_xp(self, exp_to_add):
self.exp += exp_to_add
for i in range(self.level, 51):
if self.exp > 200 * i:
if self.exp >= 200 * i:
if i > self.level:
# We leveled up!
self.level = i
return (True, "Start level: {} New Level: {}".format(self.level, i))
return (True, "Congratulations your monster leveled up!\n\nStart level: {} New Level: {}".format(self.level, i))
else:
break
return (False,)
return (False,None)
def get_current_posture(self):
return TemperamentPosture[self.temperament_posture]
......@@ -524,7 +645,7 @@ class Monster:
return int(math.floor(3*self.get_dexterity()/4) + self.get_combat_skill())
def get_evasion(self):
job_eva = evasion_by_job[self.main_job]
job_eva = adjustments_by_job[self.main_job]
base_eva = int(round(max(job_eva['base_eva'], self.level * job_eva['eva_per_level'])))
return base_eva + int(self.get_agility() / 2)
......@@ -544,12 +665,17 @@ class Monster:
def get_intelligence(self):
return int(round(max(self.base_int, self.level * self.int_per_level)))
def get_charisma(self):
return int(round(max(self.base_chr, self.level * self.chr_per_level)))
def get_base_defense(self):
return (math.floor(self.get_vitality()/2) + 8 + self.level)
def get_hp_percent(self):
print(self.hp)
return max(0, int(round((self.hp/self.get_hp())*100)))
return max(1, int(round((self.hp/self.get_hp())*100)))
def get_magic_evasion(self):
return max(1, int(1+round(self.level/4)))
def get_hit_rate(self, enemy_eva, level_difference):
rate = 75 + math.floor(((self.get_accuracy() - enemy_eva)/2)) - (2*level_difference)
......@@ -559,54 +685,34 @@ class Monster:
rate = 95
return rate
def get_physical_ws_damage(self, base_damage, modifiers):
level_range_alpha = 1.01 - (math.floor(float(self.level) / 5.0) / 100.0)
wsc = 0
for modifier in modifiers:
if modifier['stat'] == 'str':
wsc += (modifer['percent'] / 100) * self.get_strength()
elif modifiers['stat'] == 'vit':
wsc += (modifer['percent'] / 100) * self.get_vitality()
elif modifiers['stat'] == 'dex':
wsc += (modifer['percent'] / 100) * self.get_dexterity()
elif modifiers['stat'] == 'agi':
wsc += (modifer['percent'] / 100) * self.get_agility()
elif modifiers['stat'] == 'eva':
wsc += (modifer['percent'] / 100) * self.get_evasion()
elif modifiers['stat'] == 'int':
wsc += (modifer['percent'] / 100) * self.get_intelligence()
elif modifiers['stat'] == 'mnd':
wsc += (modifer['percent'] / 100) * self.get_mind()
wsc = (wsc * level_range_alpha)
bd = float(self.weapon_base_damage)
if attack_type == 'ranged':
bd += self.ammo_damage
fstr = ((self.get_strength() - enemy_vit)+4) / 4
final_damage = bd + fstr + wsc
def get_magic_hit_rate(self, spell, enemy):
# Calculate the magic accuracy
spell_data = Spells().get_spell_data(spell)
# Get the stat for the spell and base magic accuracy for that spells MA
magic_acc_stat = spell_data['magic_acc_stat']
if magic_acc_stat == 'int':
dSTAT = self.get_intelligence() - enemy.get_intelligence()
elif magic_acc_stat == 'mnd':
dSTAT = self.get_mind() - enemy.get_mind()
elif magic_acc_stat == 'chr':
dSTAT = self.get_charisma() - enemy.get_charisma()
elif magic_acc_stat == 'agi':
dSTAT = self.get_agility() - enemy.get_agility()
magic_hit_rate = 50 - 0.5 * (enemy.get_magic_evasion() - dSTAT)
if magic_hit_rate < 5:
magic_hit_rate = 5
elif magic_hit_rate > 95:
magic_hit_rate = 95
return magic_hit_rate
def get_pdif(self, base_damage, defense, attack_type, level_difference):
return final_damage
#TODO: Add fTP calculation and PDIF
#final_damage = (bd )
#return final_damage
# 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, enemy_vit):
#print("Defense: {} Type: {} Level diff: {}".format(defense, attack_type, level_difference))
# Calculate the attack/defense ratio
bd = float(self.weapon_base_damage)
if attack_type == 'ranged':
bd += self.ammo_damage
if defense == 0:
ratio = 99
else:
ratio = bd / float(defense)
ratio = base_damage / float(defense)
if attack_type == 'ranged':
cap = 3.0
else:
......@@ -646,17 +752,151 @@ class Monster:
# 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
return pDif_rand * cRatio
def get_physical_ws_damage(self, defense, attack_type, fTP, modifiers, level_difference, enemy_vit):
level_range_alpha = 1.01 - (math.floor(float(self.level) / 5.0) / 100.0)
wsc = 0
for modifier in modifiers:
if modifier['stat'] == 'str':
wsc += (modifer['percent'] / 100) * self.get_strength()
elif modifiers['stat'] == 'vit':
wsc += (modifer['percent'] / 100) * self.get_vitality()
elif modifiers['stat'] == 'dex':
wsc += (modifer['percent'] / 100) * self.get_dexterity()
elif modifiers['stat'] == 'agi':
wsc += (modifer['percent'] / 100) * self.get_agility()
elif modifiers['stat'] == 'eva':
wsc += (modifer['percent'] / 100) * self.get_evasion()
elif modifiers['stat'] == 'int':
wsc += (modifer['percent'] / 100) * self.get_intelligence()
elif modifiers['stat'] == 'mnd':
wsc += (modifer['percent'] / 100) * self.get_mind()
wsc = (wsc * level_range_alpha)
bd = float(self.weapon_base_damage)
if attack_type == 'ranged':
bd += self.ammo_damage
fstr = ((self.get_strength() - enemy_vit)+4) / 4
# These are specific to each WS
fTP_1000 = fTP[0]
fTP_2000 = fTP[1]
fTP_3000 = fTP[2]
if self.tp < 1000:
return
if 1000 <= self.tp <= 2000:
prev_anchor = fTP_1000
next_anchor = fTP_2000
dTP = float(self.tp - 1000) / 1000.0
elif 2000 <= self.tp <= 3000:
prev_anchor = fTP_2000
next_anchor = fTP_3000
dTP = float(self.tp - 2000) / 1000.0
fTP = prev_anchor + (dTP * (next_anchor - prev_anchor))
pDif = self.get_pdif(bd, defense, attack_type, level_difference)
final_damage = int((bd + fstr + wsc) * fTP * pDif)
return final_damage
def get_magic_base_damage(self, spell, enemy):
if spell in ['Banish', 'Holy']:
dINT = self.get_mind() - enemy.get_mind()
else:
dINT = self.get_intelligence() - enemy.get_intelligence()
spell_data = Spells().get_spell_data(spell)
base_damage = 0
if dINT < 0:
# multiplier ignored since dINT is too low.
base_damage = spell_data['base_damage'] + dINT
elif 0 <= dINT <= 150:
base_damage = spell_data['base_damage'] + (dINT * spell_data['multiplier'])
if base_damage < 0:
base_damage = 0
# We can skip multi-target damage reduction. There is no such thing since we only have a single enemy.
# Calculate the hit rate so we can determine if the resistance applied to the base damage.
resist_level = 0
for i in range(0, 3):
if not self.is_a_magic_hit(spell, enemy):
resist_level += 1
base_damage = base_damage / 2
if resist_level == 3:
base_damage = 0
# TODO: Add weather later maybe
# Not adding in magic burst since it is primarily a player thing
base_damage *= enemy.target_magic_damage_adjustment
return base_damage
# Calculate the base damage against the provided defense
# attack_type can be 'ranged' or 'melee'
# TODO: Replace enemyvit with the actual enemy variable
def get_physical_base_damage(self, defense, attack_type, level_difference, enemy_vit):
#print("Defense: {} Type: {} Level diff: {}".format(defense, attack_type, level_difference))
# Calculate the attack/defense ratio
bd = float(self.weapon_base_damage)
if attack_type == 'ranged':
bd += self.ammo_damage
pDif = self.get_pdif(bd, defense, attack_type, level_difference)
fstr = ((self.get_strength() - enemy_vit)+4) / 4
log("Str: {} Vit: {} fstr: {}".format(self.get_strength(), enemy_vit, fstr))
bd = bd + fstr
final_damage = int((bd * (pDif_rand * cRatio)))
final_damage = int(bd * pDif)
return final_damage
def attack(self, monster):
phy_damage = self.get_physical_base_damage(monster.get_base_defense(), 'melee', monster.level - self.level, monster.get_vitality())
self.tp += 64
return (phy_damage, 'hits')
is_a_crit = False
is_a_hit = False
spell = None
resisted = False
damage = 0
######
# TODO: Build some type of AI / testing to find the most powerful attack and use that (magic, melee, or ranged)
######
spell = 'Fire'
if self.is_a_magic_hit(spell, monster):
damage = self.get_magic_base_damage(spell, monster)
if damage == 0:
resisted = True
log("MAGIC DAMAGE: {}".format(damage))
is_a_hit = True
spell_data = Spells().get_spell_data(spell)
self.mp -= spell_data['mp_cost']
if self.mp < 0:
self.mp = 0
return AttackResponse(is_a_hit, damage, is_a_crit, spell, resisted)
if self.is_a_hit(monster):
# TODO: Make this follow the behavior in temprament and select the right behavior.
if self.tp >= 1000:
damage = self.get_physical_ws_damage(monster.get_base_defense(), 'melee', [1.0, 1.5, 4.0], [], monster.level - self.level, monster.get_vitality())
log("WS DAMAGE: {}".format(damage))
self.tp = 0
else:
damage = self.get_physical_base_damage(monster.get_base_defense(), 'melee', monster.level - self.level, monster.get_vitality())
print("STD DAMAGE: {}".format(damage))
self.tp += 64
is_a_hit = True
# Returns the amount of damage and if it is a crit
return AttackResponse(is_a_hit, damage, is_a_crit, spell, resisted)
def is_a_magic_hit(self, spell, monster):
hit_rate = self.get_magic_hit_rate(spell, monster)
return random.randint(1, 100) < hit_rate
def is_a_hit(self, monster):
hit_rate = self.get_hit_rate(monster.get_evasion(), monster.level - self.level)
......@@ -695,23 +935,56 @@ class Action:
self.monster = monster
class AttackAction(Action):
def __init__(self, attacker, target, damage, message):
STANDARD_MESSAGES = ['hit', 'smashed', 'walloped', 'struck', 'beat',
'knocked', 'punched', 'belted', 'decked', 'banged',
'battered', 'clipped', 'slapped', 'bashed', 'socked',
'smacked', 'thumped', 'cuffed', 'flogged', 'whacked',
'clobbered', 'swatted']
MAGIC_MESSAGES = ['cast']
CRIT_MESSAGES = ['CRITICALLY hit']
def __init__(self, attacker, target, damage, is_a_crit=False, spell=None):
self.attacker = attacker
self.target = target
self.damage = damage
self.message = message
self.spell = spell
if spell:
self.message = random.choice(AttackAction.MAGIC_MESSAGES)
elif is_a_crit:
self.message = random.choice(AttackAction.CRIT_MESSAGES)
else:
self.message = random.choice(AttackAction.STANDARD_MESSAGES)
#TODO: Modify and add resist as part of attacks
class MagicResistAction(Action):
MESSAGES = ['resisted']
def __init__(self, attacker, target, spell):
self.attacker = attacker
self.target = target
self.spell = spell
self.message = random.choice(MagicResistAction.MESSAGES)
class MissAction(Action):
def __init__(self, attacker, target, message):
MAGIC_MESSAGES = ['fizzled the spell against']
MESSAGES = ['missed', 'was unable to hit', 'attack went wide of', 'fumbled the hit on']
def __init__(self, attacker, target, spell):
self.attacker = attacker
self.target = target
self.message = message
self.spell = spell
if spell:
self.message = random.choice(MissAction.MESSAGES)
else:
self.message = random.choice(MissAction.MAGIC_MESSAGES)
class DefeatAction(Action):
def __init__(self, attacker, target, message, xp):
MESSAGES = ['was defeated', 'was smited', 'was conquered', 'was routed', 'was thwarted',
'was vanquished', 'was bested']
def __init__(self, attacker, target, xp):
self.attacker = attacker
self.target = target
self.message = message
self.message = random.choice(DefeatAction.MESSAGES)
self.xp = xp
......@@ -725,7 +998,8 @@ class Arena:
def heal_monsters(self):
self.monster1.hp = self.monster1.get_hp()
self.monster2.hp = self.monster2.get_hp()
self.monster1.mp = self.monster1.get_mp()
self.monster2.mp = self.monster2.get_mp()
def start(self):
self.heal_monsters()
......@@ -740,11 +1014,16 @@ class Arena:
else:
primary = self.monster2
secondary = self.monster1
if primary.is_a_hit(secondary):
result1 = primary.attack(secondary)
secondary.apply_damage(result1[0])
actions.append(AttackAction(primary, secondary, result1[0], result1[1]))
# TODO: Add a way to decide if the monster will use magic or melee or range
#if primary.is_a_magic_hit(spell, secondary)
result1 = primary.attack(secondary)
if result1.resisted:
actions.append(MagicResistAction(primary, secondary, result1.spell))
elif result1.is_a_hit:
secondary.apply_damage(result1.damage)
actions.append(AttackAction(primary, secondary, result1.damage, result1.is_a_crit,
result1.spell))
if secondary.hp <= 0:
level_difference = secondary.level - primary.level
if level_difference in exp_table:
......@@ -753,15 +1032,18 @@ class Arena:
xp = exp_table[15]
else:
xp = 0
actions.append(DefeatAction(primary, secondary, 'was defeated', xp))
actions.append(DefeatAction(primary, secondary, xp))
return actions
else:
actions.append(MissAction(primary, secondary, 'missed'))
if secondary.is_a_hit(primary):
result2 = secondary.attack(primary)
primary.apply_damage(result2[0])
actions.append(AttackAction(secondary, primary, result2[0], result2[1]))
actions.append(MissAction(primary, secondary, result1.spell))
result2 = secondary.attack(primary)
if result2.resisted:
actions.append(MagicResistAction(secondary, primary, result2.spell))
elif result2.is_a_hit:
primary.apply_damage(result2.damage)
actions.append(AttackAction(secondary, primary, result2.damage, result2.is_a_crit,
result2.spell))
if primary.hp <= 0:
level_difference = primary.level - secondary.level
if level_difference in exp_table:
......@@ -770,16 +1052,17 @@ class Arena:
xp = exp_table[15]
else:
xp = 0
actions.append(DefeatAction(secondary, primary, 'was defeated', xp))
actions.append(DefeatAction(secondary, primary, xp))
return actions
else:
actions.append(MissAction(secondary, primary, 'missed'))
actions.append(MissAction(secondary, primary, result2.spell))
return actions
if __name__ == "__main__":
#print Families
print("Spell List: \n{}\n".format(Spells().get_spell_list(20, job=Jobs.BLM, sub_job=Jobs.WHM)))
hunt_response = None
p = Pankration()
print("Zones: \n\n{}".format('\n'.join(p.list_zones())))
......@@ -798,7 +1081,7 @@ if __name__ == "__main__":
# print(monster.set_strategy(4, 3))
# print(monster.set_strategy(1, 1))
# print(monster.set_strategy(1, 2))
#monster.add_xp(22900)
monster.add_xp(22900)
# phy_damage = monster.get_physical_base_damage(100, 'melee', -15)
# print("Phys Attack: {}".format(phy_damage))
# print(monster.get_base_defense())
......@@ -818,8 +1101,8 @@ if __name__ == "__main__":
monster2.set_monster_name('wreckbutt 2')
print("1 The Soul Plate Shows: \n\n{}".format(monster))
print("2 The Soul Plate Shows: \n\n{}".format(monster2))
#monster2.add_xp(16900)
print("MP m1: {}\nMP m2: {}".format(monster.get_mp(), monster2.get_mp()))
monster2.add_xp(16900)
phy_damage = monster2.attack(monster)
print("Phys Attack: {}".format(phy_damage))
......@@ -836,17 +1119,19 @@ if __name__ == "__main__":
actions = battle_arena.step()
time.sleep(2)
for action in actions:
if isinstance(action, MagicResistAction):
print("{} {} {} while casting {}.".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), action.spell))
if isinstance(action, MissAction):
print("{} {} {}.".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name()))
print("{} {} {} while casting {}.".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), action.spell))
if isinstance(action, AttackAction):
print("{} {} {} for {}.".format(action.attacker.get_monster_name(), action.message, action.target.get_monster_name(), action.damage))
print("{} {} {} against {} for {}.".format(action.attacker.get_monster_name(), action.message, action.spell, action.target.get_monster_name(), action.damage))
if isinstance(action, DefeatAction):
print("{} {}. {} gains {} xp.".format(action.target.get_monster_name(), action.message, action.attacker.get_monster_name(), action.xp))
print("{} {} {} gains {} xp.".format(action.target.get_monster_name(), action.message, action.attacker.get_monster_name(), action.xp))
action.attacker.hp = 500
print("{} {}".format(action.target.hp, action.attacker.hp))
fighting = False
break
print("\n\nhp: {} {}\n\n".format(monster.hp, monster2.hp))
print("\n\nhp: {} {}\nmp: {} {}\n\n".format(monster.hp, monster2.hp, monster.mp, monster2.mp))
print("\n{} {}% - {} {}%\n".format(monster.get_monster_name(), monster.get_hp_percent(), monster2.get_monster_name(), monster2.get_hp_percent()))
......