summaryrefslogtreecommitdiff
path: root/player.py
diff options
context:
space:
mode:
authorJesusaves <cpntb1@ymail.com>2020-12-18 00:47:22 -0300
committerJesusaves <cpntb1@ymail.com>2020-12-18 00:47:22 -0300
commita42807c6c632615500a08c77e06d16ae346e46b1 (patch)
tree681d3f8b7fac9da32410b567459f9d6d354389ae /player.py
parentb1c8aa49ad2e5e310c93177b0656ede46c87cb84 (diff)
downloadserver-a42807c6c632615500a08c77e06d16ae346e46b1.tar.gz
server-a42807c6c632615500a08c77e06d16ae346e46b1.tar.bz2
server-a42807c6c632615500a08c77e06d16ae346e46b1.tar.xz
server-a42807c6c632615500a08c77e06d16ae346e46b1.zip
Registrations should be working again.
Bump client version so it is now authorized! \o/
Diffstat (limited to 'player.py')
-rw-r--r--player.py833
1 files changed, 833 insertions, 0 deletions
diff --git a/player.py b/player.py
new file mode 100644
index 0000000..30172a6
--- /dev/null
+++ b/player.py
@@ -0,0 +1,833 @@
+########################################################################################
+# 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
+########################################################################################
+# Player Module (parties, inventory, and player data)
+
+from utils import *
+from consts import *
+import sql
+import time, json, re
+from copy import copy
+from threading import Timer
+
+####################################### Private methods
+# Internal function for ApTimer, DO NOT CAST manually
+# FIXME: Maybe find a better replacement for this
+def ap_updater(token):
+ global ApTimer, Player
+
+ # TODO: What if token is no longer valid? (Maybe use queue instead?)
+ Player[token]["ap"]+=1
+ if (Player[token]["ap"] < Player[token]["max_ap"]):
+ try:
+ ApTimer[token].cancel()
+ del ApTimer[token]
+ except:
+ pass
+ ApTimer[token]=Timer(AP_REGEN_TIME_F, ap_updater, args=[token])
+ ApTimer[token].daemon=True
+ ApTimer[token].start()
+
+ return
+
+# Internal function for SQL, DO NOT CAST manually
+def sql_routine():
+ global Player, SQLTimer
+
+ #stdout("Now saving SQL data")
+ for token in Player:
+ stdout("[SQL] Now saving token \"%s\"" % token)
+ stdout(str(Player[token]))
+ clear(token, SQL_DELAY)
+
+ SQLTimer=Timer(SQL_SAVE_TIME, sql_routine)
+ SQLTimer.daemon=True
+ SQLTimer.start()
+ return
+
+# Attempts to place an item. Return inv index, -1 on error
+def inventoryplace(token):
+ # Check for a free slot or return next index
+ try:
+ i=Player[token]["inv"].index(None)
+ except:
+ i=len(Player[token]["inv"])
+
+ # Return -1 if array exceeds the maximum size
+ if (i > MAX_INV_SIZE):
+ return -1
+ else:
+ return i
+
+# Adds/Deletes AP from a user, manages the ApTimer
+def update_ap(token, newval):
+ global ApTimer, Player
+
+ running=False
+ if (Player[token]["ap"] < Player[token]["max_ap"]):
+ running=True
+
+ # Update AP
+ # TODO: Do we allow over-the-cap AP?
+ Player[token]["ap"]+=newval
+
+ # Handle the timer
+ if (not running and Player[token]["ap"] < Player[token]["max_ap"]):
+ ApTimer[token]=Timer(AP_REGEN_TIME_F, ap_updater, args=[token])
+ ApTimer[token].daemon=True
+ ApTimer[token].start()
+ if (running and Player[token]["ap"] >= Player[token]["max_ap"]):
+ # If it is not running/error: We don't care (very rare error)
+ try:
+ ApTimer[token].cancel()
+ except:
+ pass
+
+ return
+
+# Function which list all unit id on party
+def party_dupcheck(token, pid):
+ ar=[]
+ for unt in Player[token]["party_%s" % pid]:
+ ar.append(unt["unit_id"])
+ return ar
+
+# Function which list all unit id on inventory
+def inv_dupcheck(token, unique=True):
+ ar=[]
+ for unt in Player[token]["inv"]:
+ if unt is None:
+ if unique:
+ continue
+ else:
+ ar.append(0)
+ else:
+ if unique:
+ if unt["unit_id"] in ar:
+ continue
+ else:
+ ar.append(unt["unit_id"])
+ return ar
+
+# Techinically a battle function, readjusts a value
+# Rarity affects directly primary stat. We should distribute 140 points
+def readjust_status(rar, job, hp=True):
+ newv=50
+ # Imbalanced class
+ if (job == Job_Swordsman):
+ if hp:
+ newv+=30+rar
+ else:
+ newv+=10+(rar/2)
+ elif (job == Job_Mage):
+ if hp:
+ newv+=10+(rar/2)
+ else:
+ newv+=30+rar
+ # Balanced class
+ elif (job == Job_Assassin):
+ if hp:
+ newv+=15+(rar/2)
+ else:
+ newv+=25+rar
+ elif (job == Job_Archer):
+ if hp:
+ newv+=25+rar
+ else:
+ newv+=15+(rar/2)
+ # All-rounder class, with no steady growth
+ elif (job == Job_Gunner):
+ if hp:
+ newv+=20+(rar/2)
+ else:
+ newv+=20+(rar/2)
+
+ return newv
+
+def clear(token, mask=SQL_CLEAR):
+ # This function saves and clears a token
+ #########################################
+ try:
+ # Save inventory data to SQL
+ sql.save_inv(token, mask)
+
+ # Erase inventory if asked
+ if mask & SQL_CLEAR:
+ Player[token]["inv"]={}
+
+ # Save party data to SQL
+ sql.save_party(token)
+
+ # Erase parties if needed
+ if mask & SQL_CLEAR:
+ Player[token]["party_1"]={}
+ Player[token]["party_2"]={}
+ Player[token]["party_3"]={}
+
+ # Now save Player data to SQL
+ sql.save_player(token, mask)
+
+ # Erase Player Structure if needed
+ if mask & SQL_CLEAR:
+ del Player[token]
+
+ # Stop the AP regeneration if we're clearing
+ if mask & SQL_CLEAR:
+ try:
+ ApTimer[token].cancel()
+ del ApTimer[token]
+ except:
+ pass
+ except:
+ stdout("SQL ERROR: FAILED TO CLEAR TOKEN %s" % token)
+ if mask & SQL_CLEAR:
+ del Player[token]
+ if mask & SQL_CLEAR:
+ try:
+ ApTimer[token].cancel()
+ del ApTimer[token]
+ except:
+ pass
+
+ return
+
+def exp_to_lvlup(level):
+ # How much exp to rank up?
+ nmult=20.0
+ nmult+=level/35.0
+ nmult+=level/10.0
+ nmult+=level/6.0
+ nmult+=level/2.0
+ nmult=nmult**1.01
+ next_exp=int(level*nmult)
+ stdout("nmult is %.2f, exp needed: %d" % (nmult, next_exp))
+ return next_exp
+
+# Check if player ranked up
+# Return: {"code": True/False, "ap": <ap_raised>, "next": <exp_needed>}
+def check_rank_up(token):
+ rs={"code": False, "ap": 0}
+ next_exp=exp_to_lvlup(Player[token]["level"])*2
+
+ if Player[token]["exp"] >= next_exp:
+ # We leveled up! Prepare the bonuses
+ rs["code"]=True
+
+ # AP rules: Every 5 levels: +1 AP (always)
+ if Player[token]["level"] % 5 == 0:
+ rs["ap"]+=1
+ # AP rules: Every 10 levels: +1 AP (always)
+ if Player[token]["level"] % 10 == 0:
+ rs["ap"]+=1
+ # AP rules: Every level before the 20th: +1 AP (always_
+ if Player[token]["level"] < 20:
+ rs["ap"]+=1
+
+ # Apply the level up
+ Player[token]["exp"]-=next_exp
+ Player[token]["level"]+=1
+ Player[token]["max_ap"]+=rs["ap"]
+ update_ap(token, Player[token]["max_ap"]-Player[token]["ap"])
+
+ return rs
+
+# Conditional EXP attribution
+# Doesn't returns anything
+def grant_exp(token, inv_id, exp):
+ # We need some data
+ un=dl_search(allunits, "unit_id", Player[token]["inv"][inv_id]["unit_id"])
+ if un == "ERROR":
+ stdout("WARNING, Invalid unit id on GEXP: %d" % (Player[token]["inv"][inv_id]["unit_id"]))
+ return
+
+ # Max level, do nothing
+ if Player[token]["inv"][inv_id]["level"] >= un["max_level"]:
+ return
+
+ Player[token]["inv"][inv_id]["exp"]+=exp
+ return
+
+# Same as check_rank_up but for units
+# Returns True/False
+def check_level_up(token, inv_id):
+ global allunits
+
+ # Prepare un, the variable from unit database
+ un=dl_search(allunits, "unit_id", Player[token]["inv"][inv_id]["unit_id"])
+ if un == "ERROR":
+ stdout("WARNING, Invalid unit id on CLU: %d" % (Player[token]["inv"][inv_id]["unit_id"]))
+ return -1
+
+ # FIX
+ if Player[token]["inv"][inv_id]["level"] > un["max_level"]:
+ stdout("WARNING, Overlevelled unit (Lv %d/%d)" % (Player[token]["inv"][inv_id]["level"], un["max_level"]))
+ Player[token]["inv"][inv_id]["exp"]=0
+ Player[token]["inv"][inv_id]["level"]=copy(un["max_level"])
+
+ # Max level reached (FIXME why it still gets exp?!)
+ if Player[token]["inv"][inv_id]["level"] >= un["max_level"]:
+ return 0
+
+ # Calculate exp needed to level up
+ next_exp=exp_to_lvlup(Player[token]["inv"][inv_id]["level"])
+ ret=0
+ while Player[token]["inv"][inv_id]["exp"] >= next_exp:
+ ret += 1
+ Player[token]["inv"][inv_id]["exp"]-=next_exp
+ Player[token]["inv"][inv_id]["level"]+=1
+ # No overflow!
+ if Player[token]["inv"][inv_id]["level"] >= un["max_level"]:
+ Player[token]["inv"][inv_id]["exp"]=0
+ Player[token]["inv"][inv_id]["level"]=copy(un["max_level"])
+ break
+ next_exp=exp_to_lvlup(Player[token]["inv"][inv_id]["level"])
+ return ret
+
+# Calculates sell price of an unit
+# After 10 stars, the sell price overflows 10,000 and gets messy
+# So I capped it at 10,000. Further stars will only raise level weight.
+def calc_sell_price(rarity, level):
+ return min(10000, rarity**2*100)+(level*rarity)
+
+def daily_login(token):
+ # Prepare dates from player last login, and from now
+ # FIXME: "lastlogin" is updated in cycles
+ # If players pass midnight connected --> BOOM
+ # We should fill "lastlogin" at sql.load_player() only, instead of
+ # auto-updating it
+ dlcode=copy(ERR_LOGIN)
+ stdout("Last login: %s" % str(Player[token]["lastlogin"]))
+
+ # dlcode mask: (ERR_LOGIN meaning)
+ # 5 - 0 - 0 - 0
+ # ID - Monthly - Monthly - Weekly
+
+ d,m,y,w,H,M=date_from_sql(str(Player[token]["lastlogin"]))
+ td,tm,ty,tw,tH,tM=date_from_now()
+
+ # We don't really care with this bit of info
+ del H, M, tH, tM
+
+ # It's not the same: A reward is due
+ if (d!=td or m!=tm or y!=ty):
+ dlcode+=1000
+
+ # TODO: Weekly rewards
+ if (w != tw) or (w == tw and (d != td or m != tm or y != ty)):
+ # A weekly reward is due!
+ dlcode+=tw
+
+ if tw == MONDAY:
+ Player[token]["crystals"]+=50
+ elif tw == TUESDAY:
+ Player[token]["crystals"]+=50
+ elif tw == WEDNESDAY:
+ Player[token]["crystals"]+=50
+ elif tw == THURSDAY:
+ Player[token]["crystals"]+=50
+ elif tw == FRIDAY:
+ Player[token]["crystals"]+=50
+ elif tw == SATURDAY:
+ Player[token]["crystals"]+=50
+ elif tw == SUNDAY:
+ Player[token]["crystals"]+=50
+ else:
+ stdout("ERROR ERROR, UNKNOWN DAY OF WEEK %d" % tw)
+
+ # TODO: Monthly rewards
+ # TODO: Streak rewards
+
+ # Finally, get rid of this data
+ del Player[token]["lastlogin"]
+ return dlcode
+
+####################################### Public methods (Player/Client)
+def get_data(args, token):
+ stdout("Data received")
+ stdout("Now loading user ID: ```%s```" % str(args))
+ try:
+ y=json.loads(args)
+ passwd=y["passwd"]
+ if not passwd.isalnum():
+ raise Exception("Idiotic password")
+ if len(passwd) != 12:
+ raise Exception("Idiotic password")
+ vs=str(y["version"])
+ except:
+ return ERR_BAD
+
+ # Version check
+ if vs != CLIENTVERSION:
+ return ERR_OFF
+
+ # Maybe this token is already loaded to memory?
+ # FIXME: The token is not secretive enough, someone with
+ # the "token" can bypass all auths, so this is a risk.
+ try:
+ Player[token]["status"]+=1
+ Player[token]["status"]-=1
+ Player[token]["code"]=ERR_LOGIN
+ Player[token]["token"]=token
+ stdout("Player is already logged in")
+
+ # Delete user id, send payload, create user id again
+ uid=copy(Player[token]["userid"])
+ del Player[token]["userid"]
+ paydata=compress(Player[token])
+ Player[token]["userid"]=copy(uid)
+ del uid
+
+ # Remove other temporary data
+ del Player[token]["code"]
+ del Player[token]["token"]
+ return paydata
+ except:
+ pass
+
+ Player[token]=sql.load_player(token, passwd)
+
+ # Maybe something went wrong, or password is invalid
+ if (Player[token] == ERR_BAD):
+ del Player[token]
+ return ERR_BAD
+ if (Player[token] == ERR_ERR):
+ del Player[token]
+ return ERR_ERR
+
+ # Complete additional data
+ Player[token]["code"]=0
+ Player[token]["token"]=token
+
+ # Give you offline AP and refresh to NOW, round down
+ delta=(now()-Player[token]["aptime"])/AP_REGEN_TIME
+ Player[token]["ap"]=min(Player[token]["max_ap"], Player[token]["ap"]+delta)
+ Player[token]["aptime"]=now()
+
+ # This is only to start the timer if needed
+ if (Player[token]["ap"] < Player[token]["max_ap"]):
+ ApTimer[token]=Timer(AP_REGEN_TIME_F, ap_updater, args=[token])
+ ApTimer[token].daemon=True
+ ApTimer[token].start()
+
+ # Daily login rewards
+ Player[token]["code"]=daily_login(token)
+
+ # Remove UID from packet and save to JSON
+ uid=copy(Player[token]["userid"])
+ del Player[token]["userid"]
+ sjson=compress(Player[token])
+ Player[token]["userid"]=copy(uid)
+
+ # Clear temporary data, write internal big data fields
+ del uid
+ del Player[token]["code"]
+ del Player[token]["token"]
+ Player[token]["inv"]=sql.load_inv(token)
+ Player[token]["party_1"]=sql.load_party(token, 1)
+ Player[token]["party_2"]=sql.load_party(token, 2)
+ Player[token]["party_3"]=sql.load_party(token, 3)
+ # TODO: Load currency table
+ # TODO: Load event table
+
+ # Logged in
+ # {responseCode, token, status, gp, crystals, level, ap, max_ap, aptime }
+ return sjson
+
+# TODO: This returns the inventory. Should take a "page" argument
+def get_inv(args, token):
+ sjson=compress(Player[token]["inv"])
+ return sjson
+
+# Sell units. Receives a single JSON array of inventory IDs.
+def sellunits(args, token):
+ try:
+ y=json.loads(args)
+ gp=0
+ for tmp in y:
+ tmpa=int(tmp)
+ # Inventory size check (first check is not needed in a try loop...)
+ if tmpa >= len(Player[token]["inv"]):
+ raise Exception("Not in inventory")
+ if Player[token]["inv"][tmpa] == None:
+ stdout("None supplied (%d)" % tmpa)
+ raise Exception("Not in inventory")
+
+ # UNSELLABLE flag
+ un=dl_search(allunits, "unit_id",Player[token]["inv"][tmpa]["unit_id"])
+ if un["flags"] & UNSELLABLE:
+ stdout("This unit cannot be sold!")
+ raise Exception("This unit cannot be sold!")
+
+ # No duplicates
+ if y.count(tmp) != 1:
+ stdout("Duplication detected: %d" % (args.count(tmp)))
+ raise Exception("Duplicate index detected")
+
+ # Search in party
+ stdout("PARTY LOOKUP IN PROGRESS")
+ tmpb=dl_search(Player[token]["party_1"], "inv_id", tmpa)
+ if tmpb == "ERROR":
+ tmpb=dl_search(Player[token]["party_2"], "inv_id", tmpa)
+ if tmpb == "ERROR":
+ tmpb=dl_search(Player[token]["party_3"], "inv_id", tmpa)
+ if tmpb != "ERROR":
+ stdout("Unit is in party (%d)" % tmpb)
+ raise Exception("Party members can't be sold")
+ del tmpb
+
+ # No point wasting time, we have "un" so sum GP as well
+ am=calc_sell_price(un["rare"], Player[token]["inv"][tmpa]["level"])
+ if un["flags"] & DOUBLE_GP:
+ am*=2
+ gp+=am
+ del un, am
+ del tmpa
+ except:
+ return ERR_BAD
+
+ # Delete sold units and sum the GP
+ for idx in y:
+ Player[token]["inv"][idx]=None
+ Player[token]["gp"]+=int(gp)
+ sjson=compress('{"gp": %d, "profit": %d}' % (Player[token]["gp"], gp))
+ return sjson
+
+# Upgrade units. Receives a JSON array: [UNIT-TO-UPGRADE, MAT1, MAT2, MAT3...]
+# Based on invindex
+def upgrade(args, token):
+ # Data validation (party members can't be used as material)
+ try:
+ y=json.loads(args)
+ w=False
+ for tmp in y:
+ tmpa=int(tmp)
+ # Inventory size check (first check is not needed in a try loop...)
+ if tmpa >= len(Player[token]["inv"]):
+ raise Exception("Not in inventory")
+ if Player[token]["inv"][tmpa] == None:
+ stdout("None supplied (%d)" % tmpa)
+ raise Exception("Not in inventory")
+ # First entry can be in the party, but must be uppable
+ if not w:
+ w=True
+ un=dl_search(allunits, "unit_id",Player[token]["inv"][tmpa]["unit_id"])
+ stdout("Unit flags:")
+ stdout("%s" % un["flags"])
+ #if un["flags"] & NO_LVLUP: # FIXME: Makes no sense now
+ # stdout("This unit cannot level up!")
+ # raise Exception("This unit cannot level up!")
+ target_ele=un["attribute"]
+ del un
+ continue
+ # Self fusion??
+ stdout("begin: starting")
+ if y.count(tmp) != 1:
+ stdout("Duplication detected: %d" % (args.count(tmp)))
+ raise Exception("Duplicate index or self fusion detected")
+ # Search in party
+ stdout("LOOKUP IN PROGRESS")
+ tmpb=dl_search(Player[token]["party_1"], "inv_id", tmpa)
+ stdout("dl_search OK")
+ if tmpb == "ERROR":
+ tmpb=dl_search(Player[token]["party_2"], "inv_id", tmpa)
+ if tmpb == "ERROR":
+ tmpb=dl_search(Player[token]["party_3"], "inv_id", tmpa)
+ if tmpb != "ERROR":
+ stdout("Unit is in party (%d)" % tmpb)
+ raise Exception("Party members can't be used as material")
+ del tmpb
+ del tmpa, w
+ except:
+ return ERR_BAD
+
+ # Get "target" inv id
+ target=y.pop(0)
+ stdout("Target is (%d)" % target)
+
+ # Define the experience you'll get by draining the relevant indexes
+ xp=0
+ for idx in y:
+ uxp=0
+ ud=Player[token]["inv"][idx]["unit_id"]
+ un=dl_search(allunits, "unit_id", ud)
+ try:
+ r=un["rare"]
+ f=un["flags"]
+ e=un["attribute"]
+ next_exp=exp_to_lvlup(Player[token]["inv"][idx]["level"])
+ except:
+ r=1
+ f=0
+ e=-1
+ stdout("ERROR, INVALID UNIT ID: %d" % ud)
+
+ # Units with EXP_UP are always "max-level"
+ if f & EXP_UP:
+ lv=10+(r*10)
+ else:
+ lv=Player[token]["inv"][idx]["level"]
+
+ uxp+=lv*20.0*((r+1)/2.0)
+ uxp+=Player[token]["inv"][idx]["exp"]/next_exp*100.0*20.0
+
+ # Flags and same element bonus
+ if f & EXP_UP:
+ uxp*=1.5
+ if e == target_ele:
+ uxp*=1.2
+
+ # Give the exp, and remove from inventory
+ xp+=int(uxp)
+ Player[token]["inv"][idx]=None
+
+ # We now have the experience, so we grant it
+ grant_exp(token, target, xp)
+ r=check_level_up(token, target)
+
+ sjson=compress(r)
+ return sjson
+
+# args is the party ID
+def get_party(args, token):
+ try:
+ pid=int(args)
+ if (pid > MAX_PARTIES):
+ raise Exception("too many parties")
+ except:
+ return ERR_BAD
+
+ sjson=compress(Player[token]["party_%d" % pid])
+ return sjson
+
+# TODO: Obviously this is also a WIP
+# {"party_id": pid, "formation": [p1, p2, p3, p4]}
+def set_party(args, token):
+ # Standard checks
+ try:
+ y=json.loads(args)
+ tmp=y["party_id"]
+ pid=int(tmp)
+ # Maximum party number
+ if (pid > MAX_PARTIES):
+ raise Exception("too many parties")
+ # Only integers
+ for ele in y["formation"]:
+ tmp=int(ele)
+ # If the unit is not valid (in inventory), this will EXPLODE
+ un=dl_search(allunits, "unit_id", Player[token]["inv"][tmp]["unit_id"])
+ if un["flags"] & NO_PARTY:
+ raise Exception("This unit cannot be part of a party!")
+ del un
+ except:
+ return ERR_BAD
+
+ # FIXME: We can't have duplicates Oh My :o
+ #Player[token]["party_1"]=[]
+ stdout("Request to edit party %d" % pid)
+
+ # Check each request before appending
+ for i, idx in enumerate(y["formation"]):
+ stdout("Now checking (%d, %d)" % (i, idx))
+
+ # Empty the index in analysis
+ Player[token]["party_%s" % pid][i]={"unit_id": 0,
+ "inv_id": -1}
+
+ # Ignored index
+ if (idx < 0):
+ continue
+
+ # Duplicate checking!
+ if (Player[token]["inv"][idx]["unit_id"] in party_dupcheck(token, pid)):
+ return ERR_BAD
+
+ # Retrieve inventory data and replace it in player tokens
+ Player[token]["party_%s" % pid][i]["unit_id"]=Player[token]["inv"][idx]["unit_id"]
+ Player[token]["party_%s" % pid][i]["inv_id"]=idx
+
+ return ERR_DONE
+
+# Evolve units. Receives a JSON array: [UNIT-TO-EVOLVE, MAT1, MAT2]
+# Based on invindex
+def evolve(args, token):
+ # Data validation (party members can't be used as material)
+ try:
+ y=json.loads(args)
+ if len(y) != 3:
+ return ERR_BAD
+ w=False
+ for tmp in y:
+ tmpa=int(tmp)
+ # Inventory size check (first check is not needed in a try loop...)
+ if tmpa >= len(Player[token]["inv"]):
+ raise Exception("Not in inventory")
+ if Player[token]["inv"][tmpa] == None:
+ stdout("None supplied (%d)" % tmpa)
+ raise Exception("Not in inventory")
+ # First entry can be in the party, but must be MAX LEVEL and level-able
+ if not w:
+ w=True
+ un=dl_search(allunits, "unit_id",Player[token]["inv"][tmpa]["unit_id"])
+ stdout("Unit flags:")
+ stdout("%s" % un["flags"])
+ #if un["flags"] & NO_LVLUP: # FIXME: Makes no sense now
+ # stdout("This unit cannot level up!")
+ # raise Exception("This unit cannot level up!")
+ if un["max_level"] != Player[token]["inv"][tmpa]["level"]:
+ stdout("raise: Level not yet maxed")
+ raise Exception("Not yet max level!")
+ target_ele=un["attribute"]
+ target_id=un["unit_id"]
+ target_rare=un["rare"]
+ # Check if evolved version exists
+ evolved=dl_search(allunits, "unit_id",Player[token]["inv"][tmpa]["unit_id"]+1)
+ evolved_id=evolved["unit_id"] # Same as raise Exception if can't evolve
+ del un
+ continue
+ # Self fusion??
+ stdout("begin: starting")
+ if y.count(tmp) != 1:
+ stdout("Duplication detected: %d" % (args.count(tmp)))
+ raise Exception("Duplicate index or self fusion detected")
+ # Search in party
+ stdout("LOOKUP IN PROGRESS")
+ tmpb=dl_search(Player[token]["party_1"], "inv_id", tmpa)
+ stdout("dl_search OK")
+ if tmpb == "ERROR":
+ tmpb=dl_search(Player[token]["party_2"], "inv_id", tmpa)
+ if tmpb == "ERROR":
+ tmpb=dl_search(Player[token]["party_3"], "inv_id", tmpa)
+ if tmpb != "ERROR":
+ stdout("Unit is in party (%d)" % tmpb)
+ raise Exception("Party members can't be used as material")
+ # TODO: Check if it is suitable material (same rarity etc.)
+ r=False
+ tmpb=dl_search(allunits, "unit_id",Player[token]["inv"][tmpa]["unit_id"])
+ if tmpb["rare"] == target_rare:
+ if tmpb["flags"] & SUPEREVOMAT:
+ r=True
+ elif tmpb["flags"] & EVO_MAT:
+ if tmpb["attribute"] == target_ele:
+ r=True
+ elif tmpb["unit_id"] == target_id:
+ r=True
+ if not r:
+ stdout("raise: Invalid evolution material")
+ raise Exception("Invalid evolve material")
+ del tmpb
+ del tmpa, w
+ except:
+ return ERR_BAD
+
+ # Get "target" inv id
+ #target=y.pop(0)
+ stdout("Target is (%d)" % target_id)
+
+ # Evolve and remove material. Clear level/exp as well.
+ Player[token]["inv"][y[0]]["unit_id"]+=1
+ Player[token]["inv"][y[0]]["level"]=0
+ Player[token]["inv"][y[0]]["exp"]=0
+ Player[token]["inv"][y[1]]=None
+ Player[token]["inv"][y[2]]=None
+ r=ERR_OK
+
+ # TODO: Update party, NOT high priority
+ try:
+ tmpb=dl_search(Player[token]["party_1"], "inv_id", target_id)
+ if tmpb != "ERROR":
+ tmpb["unit_id"]+=1
+
+ tmpb=dl_search(Player[token]["party_2"], "inv_id", target_id)
+ if tmpb != "ERROR":
+ tmpb["unit_id"]+=1
+
+ tmpb=dl_search(Player[token]["party_3"], "inv_id", target_id)
+ if tmpb != "ERROR":
+ tmpb["unit_id"]+=1
+
+ # Now, do this cause a duplicate? If yes, unsocket it!
+ tmpa=0
+ for tmpb in Player[token]["party_1"]:
+ if tmpb["unit_id"] == evolved_id:
+ tmpa+=1
+ if tmpa > 1:
+ tmpb["unit_id"]=-1
+ tmpb["inv_id"]=-1
+ stdout("Unsocket")
+
+ tmpa=0
+ for tmpb in Player[token]["party_2"]:
+ if tmpb["unit_id"] == evolved_id:
+ tmpa+=1
+ if tmpa > 1:
+ tmpb["unit_id"]=-1
+ tmpb["inv_id"]=-1
+ stdout("Unsocket")
+
+ tmpa=0
+ for tmpb in Player[token]["party_3"]:
+ if tmpb["unit_id"] == evolved_id:
+ tmpa+=1
+ if tmpa > 1:
+ tmpb["unit_id"]=-1
+ tmpb["inv_id"]=-1
+ stdout("Unsocket")
+
+ except:
+ pass
+
+ sjson=compress(r)
+ return sjson
+
+
+# Creates a new account. Arguments: email
+def register(args, token):
+ stdout("Request to register an account: %s" % str(args))
+ # https://emailregex.com/email-validation-summary/ - RFC allows more emails
+ # But we don't want to risk compromising the database, and some are dump
+ regex = '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
+ try:
+ y=json.loads(args)
+ tmp=y["email"]
+ mail=str(tmp)
+ # Check if email is valid with above regex
+ if not re.search(regex,mail):
+ raise Exception("Not a valid email")
+ except:
+ return ERR_BAD
+
+ # TODO: "duplicate": all emails which difference consists on ponctuation
+ #email=mail.replace(".", "").replace("_", "").replace("-", "").replace("+", "")
+
+ stdout("Initial checks succeded")
+ # Check if email is already registered
+ check=sql.query_email(mail)
+ if (check != ""):
+ stdout("WARNING: Tried to register email \"%s\" (belongs to account %s)" % (mail, check))
+ return ERR_BAD
+
+ stdout("Now registering account")
+ # Register it
+ data=sql.add_player(mail)
+ if (data["userid"] <= 0):
+ return ERR_ERR
+
+ # From data, we have: userid, password
+ return compress(data)
+
+