gammatools/app.py

456 lines
15 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
2024-06-24 20:00:40 +00:00
from models import dice_model, ability_model, hp_model, character_model, encounter_model, ma_model, mutation_model, \
check_model
from schemas import DiceSchema, CharacterSchema, EncounterSchema, MentalAttackSchema, AbilitySchema, HPSchema, \
2024-06-24 20:00:40 +00:00
MutationSchema, CheckSchema
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')
mut = api.namespace('mut', description='Mutation 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')
2024-06-24 20:00:40 +00:00
check = api.namespace('check', description='Check operations')
check_model = check.model('Check', check_model )
check_schema = CheckSchema()
ability_model = ability.model('Ability', ability_model)
ability_schema = AbilitySchema()
mutation_model = mut.model('Mutation', mutation_model)
mutation_schema = MutationSchema()
hp_model = hp.model('HP', hp_model)
hp_schema = HPSchema()
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
2024-06-24 08:12:36 +00:00
errors = hp_schema.validate(data)
if errors:
return errors, 400
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-24 20:00:40 +00:00
@api.route('/roll/check', methods=['POST'])
class RollCheck(Resource):
@check.expect(check_model)
def post(self):
data = api.payload
errors = check_schema.validate(data)
if errors:
return errors, 400
ability_score = data.get('ability_score')
multiplier = data.get('multiplier')
threshold = ability_score * multiplier
rolled = roll_dices(1, 100, False).get('result')
if rolled < threshold:
return {'threshold': threshold, 'rolled': rolled, 'success': True}, 200
else:
return {'threshold': threshold, 'rolled': rolled, 'success': False}, 200
2024-06-22 13:22:39 +00:00
@api.route('/roll/tohit', methods=['GET'])
class RollToHit(Resource):
@staticmethod
def get():
2024-06-22 13:22:39 +00:00
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
2024-06-22 13:22:39 +00:00
@api.route('/coinflip', methods=['GET'])
class RollCoinflip(Resource):
@staticmethod
def get():
2024-06-22 13:22:39 +00:00
return random.choice(['Heads', 'Tails']), 200
@api.route('/roll/mutations', methods=['POST'])
class RollMutations(Resource):
@mut.expect(mutation_model)
def post(self):
data = api.payload
errors = mutation_schema.validate(data)
if errors:
return errors, 400
conscore = data.get('conscore')
intscore = data.get('intscore')
return roll_mutations(conscore, intscore), 200
@api.route('/character/generate', methods=['POST'])
2024-06-22 13:22:39 +00:00
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 = {}
if chartype == 'mutant':
character_sheet['animal-stock'] = (roll_mutant_animal())
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 = get_score_list(mental_mutations_cnt, intscore)
mutations_table['mental'] = get_mutations(mental_mutations_scores)
physical_mutations_scores = get_score_list(physical_mutations_cnt, conscore)
mutations_table['physical'] = get_mutations(physical_mutations_scores)
return mutations_table
def get_mutations(scorelist):
mut_table = Mutations()
mut_list = []
for score in scorelist:
mutation = mut_table.get_mental_mutation(score)
mut_list.append(mutation)
return mut_list
def get_score_list(count, modifier):
tmp_scores = []
for _ in range(count):
roll = roll_dices(1, 100, False).get('result') + modifier
if roll in tmp_scores:
roll = roll_dices(1, 100, False).get('result') + modifier
if roll > 100:
roll = 100
tmp_scores.append(roll)
return tmp_scores
def roll_mutant_animal():
creature = dict()
animal_stock = ['badger', 'racoon', 'hound', 'wolf', 'big-cat', 'fox',
'spider', 'lizard', 'ant', 'hornet', 'hawk', 'ostrich', 'emu', 'crocodile',
'snake', 'rabbit', 'rat', 'bear', 'elephant', 'platypus', 'bull', 'horse', 'goat', 'bat',
'silverfish', 'cockroach', 'turtle', 'gibbon', 'penguin', 'orangutan', 'chimpanzee',
'housefly', 'lobster', 'crab', 'prawn', 'pig', 'chicken', 'duck', 'parrot', 'mouse',
'heron', 'weasel', 'squirrel', 'pigeon', 'crow', 'house-cat', 'shark', 'dolphin', 'narwhal',
'buffalo']
posture = ['upright', 'prone', 'mixed']
creature['stock'] = random.choice(animal_stock)
creature['posture'] = random.choice(posture)
if creature['posture'] == 'upright' or creature['posture'] == 'mixed':
creature['arms'] = random.choice(list(range(2, 4, 2))) # if you're upright 4 is the limit
creature['legs'] = random.choice(list(range(2, 6, 2))) # same with legs
if creature['posture'] == 'prone':
creature['legs'] = random.choice(list(range(4, 8, 2))) # if you're prone, you only get legs
return creature
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()