add both types of attack matrices

This commit is contained in:
Greg Gauthier 2024-06-29 15:40:39 +01:00
parent 9246cb5900
commit f06b426d49
8 changed files with 255 additions and 54 deletions

View File

@ -4,6 +4,7 @@ from flask import Flask
from flask_cors import CORS
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.roll_ability_scores import roll_ability_scores
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_mutations import roll_mutations
from .models.models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model, \
mutation_model, \
check_model
mutation_model, check_model, pa_model
from .schemas.schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema, \
HPSchema, MutationSchema, CheckSchema
HPSchema, MutationSchema, CheckSchema, PhysicalAttackSchema
app = Flask(__name__)
CORS(app)
@ -28,6 +28,7 @@ dice = api.namespace('dice', description='Dice operations')
ability = api.namespace('ability', description='Ability operations')
hp = api.namespace('hp', description='HP operations')
ma = api.namespace('ma', description='Mental Attack operations')
pa = api.namespace('pa', description='Physical Attack operations')
mut = api.namespace('mut', description='Mutation operations')
character = api.namespace('character', description='Character operations')
encounter = api.namespace('encounter', description='Encounter operations')
@ -51,6 +52,9 @@ dice_schema = DiceSchema()
ma_model = ma.model('MA', ma_model)
ma_schema = MentalAttackSchema()
pa_model = pa.model('PA', pa_model)
pa_schema = PhysicalAttackSchema()
character_model = character.model('Character', character_model)
character_schema = CharacterSchema()
@ -58,6 +62,20 @@ encounter_model = encounter.model('Encounter', encounter_model)
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'])
class RollDice(Resource):
@dice.expect(dice_model)
@ -137,6 +155,24 @@ class RollMentalAttack(Resource):
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'])
class RollCheck(Resource):
@check.expect(check_model)
@ -152,27 +188,6 @@ class RollCheck(Resource):
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'])
class RollMutations(Resource):
@mut.expect(mutation_model)

View 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

View 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)

View 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)

View File

@ -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
@ -8,28 +8,5 @@ def roll_mental_attack(ams, dms, modifier):
mam = MentalAttackMatrix()
needed = mam.get_attack_score(ams, dms)
result["needed"] = needed
outcome = None
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
return get_attack_roll_outcome(result, modifier)
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

View File

@ -66,6 +66,44 @@ hp_model = {
'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 = {
'ams': fields.Integer(
required=True,

View File

@ -101,3 +101,39 @@ class MentalAttackSchema(Schema):
validate=validate.Range(min=-100, max=100),
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.'
)

View File

@ -1,12 +1,77 @@
import pandas as pd
import numpy as np
from math import floor
class WeaponClassAttackMatrix:
class AttackerWeaponClassMatrix:
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):
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)