add both types of attack matrices
This commit is contained in:
parent
9246cb5900
commit
f06b426d49
63
app/app.py
63
app/app.py
@ -4,6 +4,7 @@ from flask import Flask
|
|||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_restx import Api, Resource
|
from flask_restx import Api, Resource
|
||||||
|
|
||||||
|
from app.functions.role_physical_attack import roll_physical_attack
|
||||||
from .functions.build_character_sheet import build_character_sheet
|
from .functions.build_character_sheet import build_character_sheet
|
||||||
from .functions.roll_ability_scores import roll_ability_scores
|
from .functions.roll_ability_scores import roll_ability_scores
|
||||||
from .functions.roll_ability_check import roll_ability_check
|
from .functions.roll_ability_check import roll_ability_check
|
||||||
@ -12,10 +13,9 @@ from .functions.roll_encounter import roll_encounter
|
|||||||
from .functions.roll_mental_attack import roll_mental_attack
|
from .functions.roll_mental_attack import roll_mental_attack
|
||||||
from .functions.roll_mutations import roll_mutations
|
from .functions.roll_mutations import roll_mutations
|
||||||
from .models.models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model, \
|
from .models.models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model, \
|
||||||
mutation_model, \
|
mutation_model, check_model, pa_model
|
||||||
check_model
|
|
||||||
from .schemas.schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema, \
|
from .schemas.schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema, \
|
||||||
HPSchema, MutationSchema, CheckSchema
|
HPSchema, MutationSchema, CheckSchema, PhysicalAttackSchema
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
@ -28,6 +28,7 @@ dice = api.namespace('dice', description='Dice operations')
|
|||||||
ability = api.namespace('ability', description='Ability operations')
|
ability = api.namespace('ability', description='Ability operations')
|
||||||
hp = api.namespace('hp', description='HP operations')
|
hp = api.namespace('hp', description='HP operations')
|
||||||
ma = api.namespace('ma', description='Mental Attack operations')
|
ma = api.namespace('ma', description='Mental Attack operations')
|
||||||
|
pa = api.namespace('pa', description='Physical Attack operations')
|
||||||
mut = api.namespace('mut', description='Mutation operations')
|
mut = api.namespace('mut', description='Mutation operations')
|
||||||
character = api.namespace('character', description='Character operations')
|
character = api.namespace('character', description='Character operations')
|
||||||
encounter = api.namespace('encounter', description='Encounter operations')
|
encounter = api.namespace('encounter', description='Encounter operations')
|
||||||
@ -51,6 +52,9 @@ dice_schema = DiceSchema()
|
|||||||
ma_model = ma.model('MA', ma_model)
|
ma_model = ma.model('MA', ma_model)
|
||||||
ma_schema = MentalAttackSchema()
|
ma_schema = MentalAttackSchema()
|
||||||
|
|
||||||
|
pa_model = pa.model('PA', pa_model)
|
||||||
|
pa_schema = PhysicalAttackSchema()
|
||||||
|
|
||||||
character_model = character.model('Character', character_model)
|
character_model = character.model('Character', character_model)
|
||||||
character_schema = CharacterSchema()
|
character_schema = CharacterSchema()
|
||||||
|
|
||||||
@ -58,6 +62,20 @@ encounter_model = encounter.model('Encounter', encounter_model)
|
|||||||
encounter_schema = EncounterSchema()
|
encounter_schema = EncounterSchema()
|
||||||
|
|
||||||
|
|
||||||
|
@api.route('/coinflip', methods=['GET'])
|
||||||
|
class RollCoinflip(Resource):
|
||||||
|
@staticmethod
|
||||||
|
def get():
|
||||||
|
return random.choice(['Heads', 'Tails']), 200
|
||||||
|
|
||||||
|
|
||||||
|
@api.route('/roll/chance', methods=['GET'])
|
||||||
|
class RollChance(Resource):
|
||||||
|
@staticmethod
|
||||||
|
def get():
|
||||||
|
return roll_dices(1, 100, False), 200
|
||||||
|
|
||||||
|
|
||||||
@api.route('/roll/dice', methods=['POST'])
|
@api.route('/roll/dice', methods=['POST'])
|
||||||
class RollDice(Resource):
|
class RollDice(Resource):
|
||||||
@dice.expect(dice_model)
|
@dice.expect(dice_model)
|
||||||
@ -137,6 +155,24 @@ class RollMentalAttack(Resource):
|
|||||||
return roll_mental_attack(ams, dms, modifier), 200
|
return roll_mental_attack(ams, dms, modifier), 200
|
||||||
|
|
||||||
|
|
||||||
|
@api.route('/roll/attack/physical', methods=['POST'])
|
||||||
|
class RollPhysicalAttack(Resource):
|
||||||
|
@pa.expect(pa_model)
|
||||||
|
def post(self):
|
||||||
|
data = api.payload
|
||||||
|
errors = pa_schema.validate(data)
|
||||||
|
if errors:
|
||||||
|
return errors, 400
|
||||||
|
|
||||||
|
weapon_attack = data.get('weapon_attack') # to pick the attack table
|
||||||
|
dac = data.get('dac') # needed for both attacks
|
||||||
|
awc = data.get('awc') # only needed for weapon attacks
|
||||||
|
ahd = data.get('ahd') # only needed for non-weapon attacks
|
||||||
|
modifier = data.get('modifier')
|
||||||
|
|
||||||
|
return roll_physical_attack(weapon_attack, dac, modifier, awc, ahd), 200
|
||||||
|
|
||||||
|
|
||||||
@api.route('/roll/check', methods=['POST'])
|
@api.route('/roll/check', methods=['POST'])
|
||||||
class RollCheck(Resource):
|
class RollCheck(Resource):
|
||||||
@check.expect(check_model)
|
@check.expect(check_model)
|
||||||
@ -152,27 +188,6 @@ class RollCheck(Resource):
|
|||||||
return roll_ability_check(ability_score, multiplier), 200
|
return roll_ability_check(ability_score, multiplier), 200
|
||||||
|
|
||||||
|
|
||||||
@api.route('/roll/tohit', methods=['GET'])
|
|
||||||
class RollToHit(Resource):
|
|
||||||
@staticmethod
|
|
||||||
def get():
|
|
||||||
return roll_dices(1, 20, False), 200
|
|
||||||
|
|
||||||
|
|
||||||
@api.route('/roll/chance', methods=['GET'])
|
|
||||||
class RollChance(Resource):
|
|
||||||
@staticmethod
|
|
||||||
def get():
|
|
||||||
return roll_dices(1, 100, False), 200
|
|
||||||
|
|
||||||
|
|
||||||
@api.route('/coinflip', methods=['GET'])
|
|
||||||
class RollCoinflip(Resource):
|
|
||||||
@staticmethod
|
|
||||||
def get():
|
|
||||||
return random.choice(['Heads', 'Tails']), 200
|
|
||||||
|
|
||||||
|
|
||||||
@api.route('/roll/mutations', methods=['POST'])
|
@api.route('/roll/mutations', methods=['POST'])
|
||||||
class RollMutations(Resource):
|
class RollMutations(Resource):
|
||||||
@mut.expect(mutation_model)
|
@mut.expect(mutation_model)
|
||||||
|
26
app/functions/get_attack_roll_outcome.py
Normal file
26
app/functions/get_attack_roll_outcome.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from app.functions.roll_dices import roll_dices
|
||||||
|
|
||||||
|
|
||||||
|
def get_attack_roll_outcome(result, modifier=0):
|
||||||
|
outcome = 'Unknown'
|
||||||
|
raw_roll = roll_dices(1, 20, False).get('result')
|
||||||
|
needed = result['needed']
|
||||||
|
|
||||||
|
if modifier != 0:
|
||||||
|
result["original-roll"] = raw_roll
|
||||||
|
result["modifier"] = modifier
|
||||||
|
rolled = raw_roll + modifier # negative modifiers will subtract themselves naturally
|
||||||
|
result["adjusted-roll"] = rolled
|
||||||
|
else:
|
||||||
|
rolled = raw_roll
|
||||||
|
result["original-roll"] = rolled
|
||||||
|
|
||||||
|
if (rolled >= 20) and (needed <= 16):
|
||||||
|
outcome = "Devastating Success!"
|
||||||
|
elif needed <= rolled:
|
||||||
|
outcome = "Attack Successful!"
|
||||||
|
elif needed > rolled:
|
||||||
|
outcome = "Attack Failed!"
|
||||||
|
|
||||||
|
result['outcome'] = outcome
|
||||||
|
return result
|
12
app/functions/get_attack_values.py
Normal file
12
app/functions/get_attack_values.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from app.tables.physattack import AttackerWeaponClassMatrix, AttackerHitDiceMatrix
|
||||||
|
|
||||||
|
|
||||||
|
def get_weapon_class_threshold(awc, dac):
|
||||||
|
awc_table = AttackerWeaponClassMatrix()
|
||||||
|
return awc_table.get_attack_score(awc, dac)
|
||||||
|
|
||||||
|
|
||||||
|
def get_hit_dice_threshold(ahd, dac):
|
||||||
|
ahd_table = AttackerHitDiceMatrix()
|
||||||
|
return ahd_table.get_attack_score(ahd, dac)
|
||||||
|
|
32
app/functions/role_physical_attack.py
Normal file
32
app/functions/role_physical_attack.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from app.functions.get_attack_roll_outcome import get_attack_roll_outcome
|
||||||
|
from app.tables.physattack import AttackerHitDiceMatrix, AttackerWeaponClassMatrix
|
||||||
|
|
||||||
|
|
||||||
|
def roll_physical_attack(weapon_attack, dac, modifier, awc=0, ahd=0):
|
||||||
|
"""
|
||||||
|
:param weapon_attack: boolean. required. Determines which attack matrix to use.
|
||||||
|
:param dac: integer. required. defender armour class. used in both matrices.
|
||||||
|
:param modifier: integer. required. any pluses or minuses to be applied to roll
|
||||||
|
:param awc: integer. optional(*). Attacker weapon class. This is required, if weapon attack == True.
|
||||||
|
:param ahd: integer. optional(*). Attacker hit dice. This is required, if weapon attack == False.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
if weapon_attack:
|
||||||
|
if awc == 0:
|
||||||
|
print("Attacker Weapon Class is required for Weapon Attacks!")
|
||||||
|
result["outcome"] = "Attacker Weapon Class is required for Weapon Attacks!"
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
hit_table = AttackerWeaponClassMatrix()
|
||||||
|
result["needed"] = hit_table.get_attack_score(awc, dac)
|
||||||
|
else:
|
||||||
|
if ahd == 0:
|
||||||
|
print("Attacker Hit Dice is required for Non-Weapon Attacks!")
|
||||||
|
result["outcome"] = "Attacker Hit Dice is required for Non-Weapon Attacks!"
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
hit_table = AttackerHitDiceMatrix()
|
||||||
|
result["needed"] = hit_table.get_attack_score(ahd, dac)
|
||||||
|
|
||||||
|
return get_attack_roll_outcome(result, modifier)
|
@ -1,4 +1,4 @@
|
|||||||
from app.functions.roll_dices import roll_dices
|
from app.functions.get_attack_roll_outcome import get_attack_roll_outcome
|
||||||
from app.tables.mentattack import MentalAttackMatrix
|
from app.tables.mentattack import MentalAttackMatrix
|
||||||
|
|
||||||
|
|
||||||
@ -8,28 +8,5 @@ def roll_mental_attack(ams, dms, modifier):
|
|||||||
mam = MentalAttackMatrix()
|
mam = MentalAttackMatrix()
|
||||||
needed = mam.get_attack_score(ams, dms)
|
needed = mam.get_attack_score(ams, dms)
|
||||||
result["needed"] = needed
|
result["needed"] = needed
|
||||||
outcome = None
|
return get_attack_roll_outcome(result, modifier)
|
||||||
if needed < 2:
|
|
||||||
outcome = "Automatic Success!"
|
|
||||||
elif needed > 20:
|
|
||||||
outcome = "Absolutely No Chance!"
|
|
||||||
else:
|
|
||||||
raw_roll = roll_dices(1, 20, False).get('result')
|
|
||||||
if modifier != 0:
|
|
||||||
result["original-roll"] = raw_roll
|
|
||||||
result["modifier"] = modifier
|
|
||||||
rolled = raw_roll + modifier # negative modifiers will subtract themselves naturally
|
|
||||||
result["adjusted-roll"] = rolled
|
|
||||||
else:
|
|
||||||
rolled = raw_roll
|
|
||||||
result["original-roll"] = rolled
|
|
||||||
|
|
||||||
if (rolled >= 20) and (needed <= 16):
|
|
||||||
outcome = "Devastating Success!"
|
|
||||||
elif needed <= rolled:
|
|
||||||
outcome = "Attack Successful!"
|
|
||||||
elif needed > rolled:
|
|
||||||
outcome = "Attack Failed!"
|
|
||||||
|
|
||||||
result["outcome"] = outcome
|
|
||||||
return result
|
|
||||||
|
@ -66,6 +66,44 @@ hp_model = {
|
|||||||
'conscore': conscore_field
|
'conscore': conscore_field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pa_model = {
|
||||||
|
'weapon_attack': fields.Boolean(
|
||||||
|
required=True,
|
||||||
|
default=True,
|
||||||
|
description='Is the attacker using a weapon? If so, To-Hit is based on weapon class.'
|
||||||
|
),
|
||||||
|
'dac': fields.Integer(
|
||||||
|
required=True,
|
||||||
|
min=1,
|
||||||
|
max=10,
|
||||||
|
default=1,
|
||||||
|
description='The defenders armour class. This is needed for both weapon attacks and non-weapon attacks'
|
||||||
|
),
|
||||||
|
'awc': fields.Integer(
|
||||||
|
required=False,
|
||||||
|
min=1,
|
||||||
|
max=16,
|
||||||
|
default=1,
|
||||||
|
description='The attackers weapon class. This is needed for weapon attacks only.'
|
||||||
|
),
|
||||||
|
'ahd': fields.Integer(
|
||||||
|
required=False,
|
||||||
|
min=1,
|
||||||
|
max=16,
|
||||||
|
default=1,
|
||||||
|
description='The attackers hit dice count. This is needed for non-weapon attacks only.'
|
||||||
|
),
|
||||||
|
'modifier': fields.Integer(
|
||||||
|
required=False,
|
||||||
|
min=-100,
|
||||||
|
max=100,
|
||||||
|
default=0,
|
||||||
|
description='The roll modifier to be applied to the hit roll.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ma_model = {
|
ma_model = {
|
||||||
'ams': fields.Integer(
|
'ams': fields.Integer(
|
||||||
required=True,
|
required=True,
|
||||||
|
@ -101,3 +101,39 @@ class MentalAttackSchema(Schema):
|
|||||||
validate=validate.Range(min=-100, max=100),
|
validate=validate.Range(min=-100, max=100),
|
||||||
description='Roll modifier for mental attack'
|
description='Roll modifier for mental attack'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PhysicalAttackSchema(Schema):
|
||||||
|
weapon_attack = fields.Boolean(
|
||||||
|
required=True,
|
||||||
|
default=True,
|
||||||
|
description="Is the attacker using a weapon? If so, To-Hit is based on weapon class."
|
||||||
|
)
|
||||||
|
dac = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
min=1,
|
||||||
|
max=10,
|
||||||
|
default=1,
|
||||||
|
description='The defenders armour class. This is needed for both weapon attacks and non-weapon attacks'
|
||||||
|
)
|
||||||
|
awc = fields.Integer(
|
||||||
|
required=False,
|
||||||
|
min=1,
|
||||||
|
max=16,
|
||||||
|
default=1,
|
||||||
|
description='The attackers weapon class. This is needed for weapon attacks only.'
|
||||||
|
)
|
||||||
|
ahd = fields.Integer(
|
||||||
|
required=False,
|
||||||
|
min=1,
|
||||||
|
max=16,
|
||||||
|
default=1,
|
||||||
|
description='The attackers hit dice count. This is needed for non-weapon attacks only.'
|
||||||
|
)
|
||||||
|
modifier = fields.Integer(
|
||||||
|
required=False,
|
||||||
|
min=-100,
|
||||||
|
max=100,
|
||||||
|
default=0,
|
||||||
|
description='The roll modifier to be applied to the hit roll.'
|
||||||
|
)
|
||||||
|
@ -1,12 +1,77 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
from math import floor
|
||||||
|
|
||||||
|
|
||||||
class WeaponClassAttackMatrix:
|
class AttackerWeaponClassMatrix:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
data = {}
|
# Define the range for X-axis (column) and Y-axis (row).
|
||||||
|
x_range = list(range(1, 17)) # attacker weapon class
|
||||||
|
y_range = list(range(1, 11)) # defender armour class
|
||||||
|
|
||||||
|
# Provide your static values here as per the intersection
|
||||||
|
values = [
|
||||||
|
# Attacker Weapon class
|
||||||
|
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||||||
|
[19, 19, 18, 15, 13, 16, 14, 18, 18, 16, 16, 16, 12, 14, 14, 12], # DAC 1
|
||||||
|
[17, 18, 17, 14, 12, 15, 13, 17, 16, 15, 15, 15, 11, 13, 13, 11], # DAC 2
|
||||||
|
[16, 16, 16, 12, 10, 15, 12, 16, 15, 14, 15, 15, 8, 12, 13, 11], # DAC 3
|
||||||
|
[15, 14, 15, 12, 10, 15, 11, 15, 14, 13, 15, 15, 8, 11, 13, 18], # DAC 4
|
||||||
|
[14, 13, 14, 12, 10, 15, 10, 14, 13, 12, 14, 15, 8, 11, 13, 11], # DAC 5
|
||||||
|
[13, 12, 13, 12, 10, 15, 9, 13, 12, 11, 11, 15, 8, 10, 13, 11], # DAC 6
|
||||||
|
[12, 11, 12, 12, 10, 13, 8, 12, 11, 10, 10, 11, 8, 10, 13, 11], # DAC 7
|
||||||
|
[11, 10, 11, 12, 10, 13, 7, 11, 10, 9, 9, 9, 8, 9, 13, 11], # DAC 8
|
||||||
|
[10, 9, 10, 12, 10, 7, 6, 10, 9, 8, 7, 6, 8, 8, 8, 11], # DAC 9
|
||||||
|
[9, 8, 9, 11, 9, 6, 5, 9, 8, 7, 6, 5, 8, 8, 8, 10] # DAC 10
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create the DataFrame.
|
||||||
|
self.table = pd.DataFrame(values, columns=x_range, index=y_range)
|
||||||
|
|
||||||
|
def get_attack_score(self, awc, dac):
|
||||||
|
# pandas uses a 'column-major' order
|
||||||
|
# So, (X,Y) method arguments become (Y,X)
|
||||||
|
# for pandas locators.
|
||||||
|
return int(self.table.loc[dac, awc])
|
||||||
|
|
||||||
|
def get_matrix(self):
|
||||||
|
return self.table
|
||||||
|
|
||||||
|
def dump_matrix(self):
|
||||||
|
print(self.table)
|
||||||
|
|
||||||
|
|
||||||
class HitDiceAttackMatrix:
|
class AttackerHitDiceMatrix:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
data = {}
|
# Define the range for X-axis (column) and Y-axis (row).
|
||||||
|
x_range = list(range(1, 17)) # attacker weapon class
|
||||||
|
y_range = list(range(1, 11)) # defender armour class
|
||||||
|
|
||||||
|
values = [
|
||||||
|
# Attacker Hit Dice
|
||||||
|
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||||||
|
[20, 19, 19, 18, 18, 17, 17, 17, 16, 16, 15, 15, 15, 15, 14, 14], # DAC 1
|
||||||
|
[19, 18, 18, 17, 17, 16, 16, 16, 15, 15, 14, 14, 14, 14, 13, 13], # DAC 2
|
||||||
|
[18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 13, 13, 13, 13, 12, 12], # DAC 3
|
||||||
|
[17, 16, 16, 15, 15, 14, 14, 14, 13, 13, 12, 12, 12, 12, 11, 11], # DAC 4
|
||||||
|
[16, 15, 15, 14, 14, 13, 13, 13, 12, 12, 11, 11, 11, 11, 10, 10], # DAC 5
|
||||||
|
[14, 13, 13, 12, 12, 11, 11, 11, 10, 10, 9, 9, 9, 9, 8, 8], # DAC 6
|
||||||
|
[13, 12, 12, 11, 11, 10, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7], # DAC 7
|
||||||
|
[12, 11, 11, 10, 10, 9, 9, 9, 8, 8, 7, 7, 7, 7, 6, 6], # DAC 8
|
||||||
|
[11, 10, 10, 9, 9, 8, 8, 8, 7, 7, 6, 6, 6, 6, 5, 5], # DAC 9
|
||||||
|
[10, 9, 9, 8, 8, 7, 7, 7, 6, 6, 5, 5, 5, 5, 4, 4] # DAC 10
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create the DataFrame.
|
||||||
|
self.table = pd.DataFrame(values, columns=x_range, index=y_range)
|
||||||
|
|
||||||
|
def get_attack_score(self, ahd, dac):
|
||||||
|
# pandas uses a 'column-major' order
|
||||||
|
# So, (X,Y) method arguments become (Y,X)
|
||||||
|
# for pandas locators.
|
||||||
|
return int(self.table.loc[dac, ahd])
|
||||||
|
|
||||||
|
def get_matrix(self):
|
||||||
|
return self.table
|
||||||
|
|
||||||
|
def dump_matrix(self):
|
||||||
|
print(self.table)
|
||||||
|
Loading…
Reference in New Issue
Block a user