gammatools/app.py

384 lines
12 KiB
Python
Raw Normal View History

from flask import Flask
2024-06-22 21:29:19 +00:00
from flask_cors import CORS
2024-06-22 13:22:39 +00:00
from flask_restx import Api, Resource
2024-06-23 11:13:42 +00:00
from models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model
from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema
2024-06-22 22:56:55 +00:00
from encounters import EncounterTable
2024-06-23 11:13:42 +00:00
from mentattack import MentalAttackMatrix
from mutations import Mutations
from cybermods import CyberMods
2024-06-21 21:49:11 +00:00
import random
app = Flask(__name__)
2024-06-22 21:29:19 +00:00
CORS(app)
app.config.SWAGGER_UI_JSONEDITOR = True
app.config['SWAGGER_UI_JSONEDITOR'] = True
api = Api(app, version='1.0', title='Gamma World Dice', description='Rolled Dice As A Service')
dice = api.namespace('dice', description='Dice operations')
ability = api.namespace('ability', description='Ability operations')
hp = api.namespace('hp', description='HP operations')
2024-06-23 11:13:42 +00:00
ma = api.namespace('ma', description='Mental Attack operations')
2024-06-22 13:22:39 +00:00
character = api.namespace('character', description='Character operations')
2024-06-22 22:56:55 +00:00
encounter = api.namespace('encounter', description='Encounter operations')
ability_model = ability.model('Ability', ability_model)
ability_schema = AbilitySchema()
hp_model = hp.model('HP', hp_model)
# TODO: Add hitpoint schema
dice_model = dice.model('Dice', dice_model)
dice_schema = DiceSchema()
ma_model = ma.model('MA', ma_model)
ma_schema = MentalAttackSchema()
character_model = character.model('Character', character_model)
2024-06-22 13:22:39 +00:00
character_schema = CharacterSchema()
encounter_model = encounter.model('Encounter', encounter_model)
2024-06-22 22:56:55 +00:00
encounter_schema = EncounterSchema()
@api.route('/roll/dice', methods=['POST'])
class RollDice(Resource):
@dice.expect(dice_model)
def post(self):
data = api.payload
errors = dice_schema.validate(data)
if errors:
return errors, 400
quantity = data.get('quantity')
geometry = data.get('geometry')
discard_lowest = data.get('discard_lowest')
if quantity is None or geometry is None:
return {"message": "Required dice data not provided"}, 400
return roll_dices(quantity, geometry, discard_lowest), 200
@api.route('/roll/ability', methods=['POST'])
class RollAbility(Resource):
@ability.expect(ability_model)
def post(self):
data = api.payload
errors = ability_schema.validate(data)
if errors:
return errors, 400
chartype = data.get('chartype')
attribute = data.get('ability')
return roll_ability_scores(chartype, attribute), 200
@api.route('/roll/hp', methods=['POST'])
class RollHP(Resource):
@hp.expect(hp_model)
def post(self):
data = api.payload
chartype = data.get('chartype')
conscore = data.get('conscore')
if conscore is None:
return {"message": "A constitution score is required"}, 400
if chartype == 'human':
geometry = 8
else:
geometry = 6
return roll_dices(conscore, geometry, False), 200
2024-06-22 22:56:55 +00:00
@api.route('/roll/encounter', methods=['POST'])
class RollEncounter(Resource):
@encounter.expect(encounter_model)
def post(self):
data = api.payload
errors = encounter_schema.validate(data)
if errors:
return errors, 400
terrain = data.get('terrain').lower()
roll = roll_dices(1, 20, False).get('result')
et = EncounterTable()
creature = et.get_encounter(roll, terrain)
if creature is None:
creature = "All clear!"
return {"encounter": creature}, 200
2024-06-23 11:13:42 +00:00
@api.route('/roll/attack/mental', methods=['POST'])
class RollMentalAttack(Resource):
@ma.expect(ma_model)
def post(self):
data = api.payload
errors = ma_schema.validate(data)
if errors:
return errors, 400
ams = data.get('ams')
dms = data.get('dms')
modifier = data.get('modifier')
result = {}
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
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, 200
2024-06-22 13:22:39 +00:00
@api.route('/roll/tohit', methods=['GET'])
class RollToHit(Resource):
def get(self):
return roll_dices(1, 20, False), 200
@api.route('/roll/chance', methods=['GET'])
class RollChance(Resource):
def get(self):
return roll_dices(1, 100, False), 200
2024-06-22 13:22:39 +00:00
@api.route('/coinflip', methods=['GET'])
class RollCoinflip(Resource):
def get(self):
2024-06-22 13:22:39 +00:00
return random.choice(['Heads', 'Tails']), 200
@api.route('/character/generate')
class GenerateCharacter(Resource):
@character.expect(character_model)
def post(self):
data = api.payload
errors = character_schema.validate(data)
if errors:
return errors, 400
chartype = data.get('chartype')
character_sheet = {}
ability_scores = roll_ability_scores(chartype)
character_sheet['abilities'] = ability_scores
character_sheet['hp'] = roll_hp(chartype, ability_scores['constitution'])
character_sheet['gold'] = roll_dices(4, 4, False).get('result') * 10
character_sheet['domar'] = roll_dices(2, 4, False).get('result') * 5
2024-06-22 13:22:39 +00:00
if chartype == 'humanoid' or chartype == 'mutant':
character_sheet['mutations'] = (
roll_mutations(ability_scores['constitution'], ability_scores['intelligence'])
)
if chartype == 'cyborg':
character_sheet['cybermods'] = (
roll_cybermods()
)
2024-06-22 13:22:39 +00:00
return character_sheet, 200
2024-06-21 21:49:11 +00:00
def roll_dices(dice_count, dice_sides, discard_lowest):
roll_results = []
discarded = []
2024-06-21 21:49:11 +00:00
for _ in range(dice_count):
roll_results.append(random.randint(1, dice_sides))
if discard_lowest and len(roll_results) > 0:
discarded.append(min(roll_results))
2024-06-21 21:49:11 +00:00
roll_results.remove(min(roll_results))
if discarded:
2024-06-22 13:22:39 +00:00
net_dice_count = dice_count - 1
mnemonic = str(dice_count) + "(-1)d" + str(dice_sides)
else:
2024-06-22 13:22:39 +00:00
mnemonic = str(dice_count) + 'd' + str(dice_sides)
net_dice_count = dice_count
2024-06-21 21:49:11 +00:00
result = {
'dice-set': {
'mnemonic': mnemonic,
'min-roll': net_dice_count,
2024-06-22 13:22:39 +00:00
'max-roll': dice_sides * net_dice_count
},
2024-06-21 21:49:11 +00:00
'rolls': roll_results,
'result': sum(roll_results)
2024-06-21 21:49:11 +00:00
}
if discarded:
result['discarded'] = discarded
2024-06-21 21:49:11 +00:00
return result
def roll_ability_scores(chartype, attribute=None):
if attribute is None or attribute == "all":
ability_list = ['p-strength', 'm-strength', 'constitution', 'dexterity', 'charisma', 'intelligence']
2024-06-22 13:22:39 +00:00
else:
ability_list = [attribute]
2024-06-22 13:22:39 +00:00
ability_scores = {}
for abil in ability_list:
if ((chartype == 'human') and
((abil == 'intelligence') or
(abil == 'charisma') or
(abil == 'constitution'))):
roll = roll_dices(4, 6, False) # humans are special
if (abil == 'constitution') and roll["result"] > 18:
roll["capped"] = True
roll["result"] = 18
if (abil == 'intelligence' or abil == 'charisma') and roll["result"] > 21:
roll["capped"] = True
roll["result"] = 21
ability_scores[abil] = roll["result"]
else:
roll = roll_dices(4, 6, True) # nothing else is special
ability_scores[abil] = roll["result"]
2024-06-22 13:22:39 +00:00
return ability_scores
2024-06-22 13:22:39 +00:00
def roll_hp(chartype, conscore):
if chartype == 'human':
geometry = 8
else:
geometry = 6
return roll_dices(conscore, geometry, False).get('result')
def roll_cybermods():
"""
Cybermods are similar to mutations, except that they are artificial changes to the body,
where mutations are 'natural' changes to the body. Fewer cybermods are possible than with
mutations. So, you make one roll of 1d4, and then parse up the score between the two
types of mods, as you see fit. If no emphasis is specified, the score will be split as evenly
as possible. If the score is 1, then a physical mod will be the default.
:return: table of cybermods (in json format)
"""
cybermods_table = {}
cybermods_count = roll_dices(1, 4, False)["result"]
2024-06-23 17:37:58 +00:00
phys_cnt = split_number(cybermods_count)[1] # This ends up being the higher of the two numbers
ment_cnt = split_number(cybermods_count)[0]
2024-06-23 17:34:37 +00:00
cybermods_table['count'] = {'mental': ment_cnt, 'physical': phys_cnt}
mental_cybermods_scores = []
physical_cybermods_scores = []
for _ in range(ment_cnt):
mscore = roll_dices(1, 10, False).get('result')
if mscore in mental_cybermods_scores:
mscore = roll_dices(1, 10, False).get('result')
mental_cybermods_scores.append(mscore)
for _ in range(phys_cnt):
pscore = roll_dices(1, 10, False).get('result')
if pscore in physical_cybermods_scores:
pscore = roll_dices(1, 10, False).get('result')
physical_cybermods_scores.append(pscore)
cyb = CyberMods()
mcyb = []
pcyb = []
for score in mental_cybermods_scores:
modification = cyb.get_mental_cybermod(score)
mcyb.append(modification)
if len(mcyb) != 0:
cybermods_table['mental'] = mcyb
for score in physical_cybermods_scores:
modification = cyb.get_physical_cybermod(score)
pcyb.append(modification)
if len(pcyb) != 0:
cybermods_table['physical'] = pcyb
return cybermods_table
def roll_mutations(conscore, intscore):
"""
:param conscore: modifier for physical mutation determination
:param intscore: modifier for mental mutation determination
:return: table of mutations (in json form)
"""
mutations_table = {}
2024-06-22 21:29:19 +00:00
mental_mutations_cnt = roll_dices(1, 4, False).get('result')
physical_mutations_cnt = roll_dices(1, 4, False).get('result')
mutations_table['count'] = {'mental': mental_mutations_cnt, 'physical': physical_mutations_cnt}
mental_mutations_scores = []
physical_mutations_scores = []
for _ in range(mental_mutations_cnt):
mscore = roll_dices(1, 100, False).get('result') + intscore
if mscore in mental_mutations_scores:
mscore = roll_dices(1, 100, False).get('result') + intscore
if mscore > 100:
mscore = 100
mental_mutations_scores.append(mscore)
for _ in range(physical_mutations_cnt):
pscore = roll_dices(1, 100, False).get('result') + conscore
if pscore in physical_mutations_scores:
pscore = roll_dices(1, 100, False).get('result') + conscore
if pscore > 100:
pscore = 100
physical_mutations_scores.append(pscore)
mut = Mutations()
mmut = []
pmut = []
for score in mental_mutations_scores:
mutation = mut.get_mental_mutation(score)
mmut.append(mutation)
mutations_table['mental'] = mmut
for score in physical_mutations_scores:
mutation = mut.get_physical_mutation(score)
pmut.append(mutation)
mutations_table['physical'] = pmut
return mutations_table
def split_number(n):
first_split = n // 2 # this will split the number in two equal parts, if it is even
second_split = n % 2 + first_split # this will add one to one part if the number is odd
return first_split, second_split
2024-06-21 21:49:11 +00:00
if __name__ == '__main__':
app.run()