########################################################################################
# This file is part of Spheres.
# Copyright (C) 2019 Jesusalva
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
########################################################################################
# Battle Module - Main module
from utils import stdout, compress, allunits, Player, Battle, allquests
from consts import (ERR_ERR, ERR_BAD, ST_TOWN, ST_QUEST, ACT_SPHERE,
SPH_WIDEATTACK, SPH_PIERCE, SPH_ASSAULT, SPH_HEAL, SPH_HEALALL, SPH_ATKUP,
SPH_DEFUP, SPH_NONE)
import json, random, traceback
import player
from battle.common import (find_target, calc_dmg, conditions, advance_wave,
battle_endturn)
from battle.spheres import sphere_attack
#from battle.summons import summon # <-- Protocol?
#from battle.skills import skill_core, handle_skill # <-- WIP
from copy import copy
# Prepare a randomness seed
random.seed()
#############################################
# Client commands
#############################################
# begin_quest( world, quest_id, party_id )
def begin_quest(args, token, client_side=True):
global allquests, Player, Battle
# Data validation
try:
stdout("Begin Quest: %s" % args)
bq=json.loads(args)
stdout("JSON loaded", 2)
# Validation
tmp=int(bq["quest_id"])
tmp=int(bq["party_id"])
if (tmp < 1 or tmp > 3):
return ERR_BAD
tmpw=str(bq["world"])
tmp=int(Player[token]["quest"]) # FIXME
for quest in allquests[tmpw]:
if (quest["quest_id"] == bq["quest_id"]):
break
if (quest["quest_id"] != bq["quest_id"]):
raise Exception("Quest not found")
stdout("All fine thus far", 2)
except:
stdout(traceback.format_exc(), 0)
# Invalid data
return ERR_BAD
# We have `quest` variable loaded and now we'll only use it, but...
# Can you start a quest? (In Town, Requeriments met, etc.)
if (Player[token]["status"] != ST_TOWN and client_side):
return ERR_BAD
if (Player[token]["quest"] < quest["requeriment"]):
return ERR_BAD
if (Player[token]["ap"] < quest["cost"] and client_side):
return ERR_BAD
stdout("You can begin it!", 2)
Player[token]["status"]=int(quest["quest_id"])
# client_side is an internal flag
if client_side:
#Player[token]["ap"]-=quest["cost"]
player.update_ap(token, -(quest["cost"]))
Battle[token]={
"world": bq["world"],
"quest_id": quest["quest_id"],
"difficulty": quest["difficulty"],
"result": "",
"bp": 0,
"wave": 0,
"max_wave": len(quest["waves"]),
"enemy": [],
"party": [],
"party_id": int(bq["party_id"]),
"spheres": [],
"log": [],
"turn": 1}
stdout("Data set", 2)
# Write the current wave enemies data
advance_wave(token, bq["world"], quest["quest_id"], 0)
# Write the current party (and their HP bar)
for ux in Player[token]["party_"+str(int(bq["party_id"]))]:
# If you don't have anyone here: Don't append to party
if ux["unit_id"] == 0:
continue
#stdout("FOUND AN UX: %s" % str(ux))
# Un: Entry in unit database. Ux: Entry on party.
# TODO: Maybe use dl_search instead
for un in allunits:
if (un["unit_id"] == ux["unit_id"]):
#stdout("[BEGIN_QUEST] Match found for un and ux unit_id (un is: %s)" % str(un))
break
else:
continue
# Wait, what?
if (un["unit_id"] != ux["unit_id"]):
print("Error, un and ux differs: %s vs %s" % (str(un["unit_id"]), str(ux["unit_id"])))
return ERR_ERR
stdout("Preparing party member", 2)
# How much HP/ATK bonus per level you'll get?
# Each level boosts your stats in 1%
lvl=int(Player[token]["inv"][ux["inv_id"]]["level"])-1
hpboost=int(un["hp"]*(max(lvl-1, 0)/100.0))
akboost=int(un["strength"]*(max(lvl-1, 0)/100.0))
# generate unit data for battle
Battle[token]["party"].append({
"unit_id": un["unit_id"],
"max_hp": un["hp"]+hpboost,
"hp": un["hp"]+hpboost,
"atk": un["strength"]+akboost,
"ele": un["attribute"],
"status_effects": 0
})
# Add 1~3 random spheres
i=random.randint(1,3)
while i > 0:
i-=1
Battle[token]["spheres"].append(random.choice([
SPH_WIDEATTACK,
SPH_PIERCE,
SPH_ASSAULT,
SPH_HEAL,
SPH_HEALALL,
SPH_ATKUP,
SPH_DEFUP
]))
# Add empty spheres to keep length
while (len(Battle[token]["spheres"]) < 5):
Battle[token]["spheres"].append(SPH_NONE)
sjson=compress(Battle[token])
return sjson
# Advance turn button. Main turn logic
# Receives a list of units position, and if they'll use sphere:
# { "unit": [uid1, uid2 ....], "sphere": [True/False array, in future will save skill as 2] }
def battle(args, token):
global allquests, Player, Battle
# Data validation
try:
#stdout("Advance round: %s" % args)
bt=json.loads(args)
#stdout("JSON loaded")
# Validation
tmp=len(bt["unit"])
#stdout("vu: %d" % tmp)
tmp=len(bt["sphere"])
#stdout("vu: %d" % tmp)
tmp=len(bt["action"])
#stdout("vs: %d" % tmp)
tmp=int(Player[token]["quest"]) # FIXME
#stdout("ptq: %d pts: %d" % (tmp, Player[token]["status"]))
if (Player[token]["status"] < ST_QUEST):
return ERR_BAD
#stdout("validation step 1")
# Check if all members you've told us are valid
"""
for j in bt["unit"]:
i=False
for k in Battle[token]["party"]:
if (k["unit_id"] == j["unit_id"]):
i=True
break
if not i:
stdout("Supplied data is valid: Attempt to add invalid members")
raise Exception("Attempted to add invalid members")
"""
# Check if all spheres you've told us are valid
# FIXME: bt["sphere"] should be bt["action"]
if sorted(bt["sphere"]) != sorted(Battle[token]["spheres"]):
stdout("Spheres differ: %s != %s" % (str(sorted(bt["sphere"])), str(sorted(Battle[token]["spheres"]))))
raise Exception("Different spheres")
except:
# Invalid data
traceback.print_exc()
return ERR_BAD
#######################################
# Reorder your party based on your request. We already know its valid
Battle[token]["spheres"] = copy(bt["sphere"])
bt["sphere"] = bt["action"]
"""
tmp=[]
for bat in Battle[token]["party"]:
tmp.append(bat)
Battle[token]["party"]=[]
for j in bt["unit"]:
for k in tmp:
if (j["unit_id"] == k["unit_id"]):
Battle[token]["party"].append(k)
break
"""
# Erase temporary variables
del tmp
stdout("Party reordered (SKIPPED)", 2)
Battle[token]["log"]=[]
#######################################
# Prepare temporary data
spheres=[]
for i in Battle[token]["spheres"]:
spheres.append(int(i))
print("Sphere list: "+str(spheres))
#######################################
# TODO: determine battle order
############
# Friends
for bat in Battle[token]["party"]:
# The dead can't fight
if (bat["hp"] <= 0):
continue
# Will you use a sphere?
idx=Battle[token]["party"].index(bat)
stdout("Check index %d" % idx, 2)
if (bt["sphere"][idx] == ACT_SPHERE):
# Remove the sphere
spheres[idx]=SPH_NONE
sphere_attack(token, bat, Battle[token]["spheres"][idx], idx)
Battle[token]["bp"]+=2
else:
sphere_attack(token, bat, SPH_NONE, idx)
Battle[token]["bp"]+=1
stdout("Attack performed", 2)
# If HP ends up broken
if (bat["hp"] > bat["max_hp"]):
bat["hp"]=0+bat["max_hp"]
# HOLD THAT! Handle victory/defeat conditions
check = conditions(token, spheres)
if check is not None:
return check
stdout("Friends executed", 2)
############
# Enemies
for idx, bat in enumerate(Battle[token]["enemy"]):
# The dead can't fight
if (bat["hp"] <= 0):
continue
# Simple Attack
target_id=find_target(token, "party")
stdout("ENEMY: Perform attack against %d" % target_id, 2)
target=Battle[token]["party"][target_id]
dmg=calc_dmg(token, bat, target, bat["atk"])
target["hp"]-=dmg
Battle[token]["bp"]+=1
Battle[token]["log"].append(["enemy", idx, SPH_NONE, dmg, "party", target_id])
stdout("ENEMY: Attack performed", 2)
# If HP ends up broken
if (bat["hp"] > bat["max_hp"]):
bat["hp"]=0+bat["max_hp"]
continue
# HOLD THAT! Handle victory/defeat conditions
check = conditions(token, spheres)
if check is not None:
return check
stdout("Enemies executed", 2)
return battle_endturn(token, spheres)
# Reload battle will only inform the client again of the battle status
# If in a battle, otherwise it returns: ERR_BAD
# TODO FIXME: Do not default to PID 1. Probably save it as a mask.
# eg. PID + QID as string. So ST_QUEST becomes 11 (PID 1 QID 1)
# FIXME: Token might have changed, maybe we should save to userid instead of token
def reload_battle(token):
if (Player[token]["status"] < ST_QUEST):
return ERR_BAD
try:
bq=0+int(Battle[token]["quest_id"])
del bq
return compress(Battle[token])
except:
# FIXME: Detect world
v1=begin_quest('{"world": "Main", "quest_id": %d, "party_id": 1}' % Player[token]["status"], token, client_side=False)
stdout("Restarting quest...")
return v1
#################################################
# Handles CI false positives in a lame way
def pyflakes():
return allquests