#! /usr/bin/env python3
# -*- coding: utf8 -*-
#
# Copyright (C) 2010-2011 Evol Online
# Copyright (C) 2018 TMW-2
# Author: Andrei Karas (4144)
# Author: Jesusalva
#
# The use of the data
import datetime
import sys
stgen=False
aeros=False
bifs=False
skipCI=False
wikib=open("EleMonsters.html", "w")
wikib.write('
EleGen File')
def printSeparator():
print("--------------------------------------------------------------------------------")
def showHeader():
global stgen, aeros, bifs, skipCI
print("TMW2 Ele Generator")
print("Run at: " + datetime.datetime.now().isoformat())
print("Usage: ./redesign.py [default|aeros|none|update|all] []")
if len(sys.argv) >= 2:
if sys.argv[1] == "default":
stgen=True
aeros=False
bifs=False
skipCI=False
elif sys.argv[1] == "aeros":
stgen=False
aeros=True
bifs=False
skipCI=False
elif sys.argv[1] == "none":
stgen=False
aeros=False
bifs=False
skipCI=False
elif sys.argv[1] == "update":
stgen=True
aeros=False
bifs=False
skipCI=True
elif sys.argv[1] == "all":
stgen=True
aeros=True
bifs=True
skipCI=False
else:
exit(1)
print("This stuff analyzes and sorts monsters and then create base stats for Moubootaur Legends.")
print("Drops aren't calculated or taken in account, TWEAK AS NEEDED")
printSeparator()
print("Output is: EleMonsters.html")
def showFooter():
#pass
#printSeparator()
print("Done.")
Mobs1=[]
Mobs2=[]
Mobs3=[]
Mobs4=[]
Mobs5=[]
Mobs6=[]
MobsA=[]
# [N, W, E, F, W, -, H, H, G, -]
Ele=[0, 0, 0, 0, 0, 0, 0, 0, 0]
# This is for Aeros
Plants=[]
Level50=[]
Level100=[]
Aggressive=[]
Assistant=[]
Looter=[]
Boss=[]
SysDrops=[]
def fwalk(wmask):
if wmask == 'WATER':
return '%s' % (wmask)
elif wmask == 'AIR':
return '%s' % (wmask)
elif wmask == 'WALL':
return '%s' % (wmask)
elif wmask == 'NORMAL' or wmask == 'DEFAULT':
return '%s' % (wmask)
else:
print("Invalid walk mask: "+wmask)
exit(1)
def WhatRace(rac):
rc=rac.race
if rc == 0:
return "Formless"
elif rc == 1:
return "Undead"
elif rc == 2:
return "Brute"
elif rc == 3:
return "Plant"
elif rc == 4:
return "Insect"
elif rc == 5:
return "Mineral"
elif rc == 6:
return "-"
elif rc == 7:
return "SemiHuman"
elif rc == 8:
return "Angel"
elif rc == 9:
return "Dragon"
elif rc == 10:
return "Player"
elif rc == 11:
return "Boss"
elif rc == 12:
return "NonBoss"
elif rc == 14:
return "NonSemiHuman"
elif rc == 15:
return "NonPlayer"
elif rc == 16:
return "SemiPlayer"
elif rc == 17:
return "NonSemiPlayer"
else:
print("ERROR, INVALID RACE ID: %d (ID: %s)" % (rc, rac.id))
exit(1)
def WhatElem(rac):
rc=rac.elem
tl="ERROR"
cl="#F00"
if rc == 0:
tl,cl="Neutral","#000"
elif rc == 1:
tl,cl="Water","#00F"
elif rc == 2:
tl,cl="Nature","#7A0"
elif rc == 3:
tl,cl="Fire","#F00"
elif rc == 4:
tl,cl="Error(W)","#093"
elif rc == 5:
tl,cl="Error(P)","#040"
elif rc == 6:
tl,cl="Holy","#afa"
elif rc == 7:
tl,cl="Dark","#908"
elif rc == 8:
tl,cl="Error(G)","#404"
elif rc == 9:
tl,cl="Error(U)","#440"
else:
print("ERROR, INVALID ELEM ID: %d (ID: %s)" % (rc, rac.id))
exit(1)
Ele[rc]+=1
return "%s" % (cl, tl)
class Mob:
def __init__(self):
# Basic
self.id="0"
#self.aegis="UnknownMonster" # SpriteName is not used anywhere, we are using its ID
self.name="Unknown Monster Name"
self.view="1"
self.boss=False
self.plant=False
# Defensive
self.mobpt="0" # Mob Points “Level”
self.hp="0"
self.xp="Exp: 0"
self.jp="JExp: 0"
self.st=""
self.dfn=0
self.mdf=0
# Offensive
self.atk="[0, 0]"
self.range="1"
self.move="0"
self.delay="0"
self.drops=[]
# New
self.race=-1
self.elem=-1
self.elel=-1
self.walk="NORMAL"
# Stats
self.str='0'
self.agi='0'
self.vit='0'
self.int='0'
self.dex='0'
self.luk='0'
def MobAlloc(ab):
try:
maab=int(ab.mobpt)
except:
maab=9901
if maab <= 20 or skipCI:
Mobs1.append(ab)
elif maab <= 40:
Mobs2.append(ab)
elif maab <= 60:
Mobs3.append(ab)
elif maab <= 80:
Mobs4.append(ab)
elif maab <= 100:
Mobs5.append(ab)
elif maab <= 150:
Mobs6.append(ab)
else:
MobsA.append(ab)
# Aeros allocation
"""Plants=[]
Level50=[]
Level100=[]
Aggressive=[]
Assistant=[]
Looter=[]
Boss=[]"""
normie=True
if ab.plant:
Plants.append(ab.id)
normie=False
if ab.boss:
Boss.append(ab.id)
normie=False
if normie:
if "Agr" in ab.st:
Aggressive.append(ab.id)
normie=False
if "Lot" in ab.st:
Looter.append(ab.id)
normie=False
if "Ass" in ab.st:
Assistant.append(ab.id)
normie=False
if normie:
if maab <= 55:
Level50.append(ab.id)
else:
Level100.append(ab.id)
def testMobs():
print("Generating Elem-Mob Wiki...")
if len(sys.argv) >= 3:
src=open(sys.argv[2]+"/db/pre-re/mob_db.conf", "r", encoding="utf-8")
else:
src=open("../../server-data/db/pre-re/mob_db.conf", "r", encoding="utf-8")
wikib.write("EleGen Monster Database
\n")
start=False
dropper=False
skip=0
x=Mob() # Only for pyflakes2
for a2 in src:
a=a2.replace(' ', '\t');
if a == "{\n":
if skip:
skip=0
if start:
MobAlloc(x)
else:
start=True
x=Mob()
if skip:
start=False
x=Mob()
continue
if " Id:" in a:
x.id=stp(a)
if x.id == "ID":
continue
if " Name:" in a:
x.name=stp(a)
elif " Hp:" in a:
x.hp=stp(a)
elif " Lv:" in a:
x.mobpt=stp(a)
elif " Exp:" in a:
x.xp=stp(a)
elif " JExp:" in a:
x.jp=stp(a)
elif " Attack:" in a:
x.atk=stp(a)
elif " AttackRange:" in a:
x.range=stp(a)
elif " MoveSpeed:" in a:
x.move=stp(a)
elif " ViewRange:" in a:
x.view=stp(a)
elif " AttackDelay:" in a:
x.delay=stp(a)
elif " Def:" in a:
x.dfn=stp(a)
elif " Mdef:" in a:
x.mdf=stp(a)
elif " Str:" in a:
x.str=stp(a)
elif " Agi:" in a:
x.agi=stp(a)
elif " Vit:" in a:
x.vit=stp(a)
elif " Int:" in a:
x.int=stp(a)
elif " Dex:" in a:
x.dex=stp(a)
elif " Luk:" in a:
x.luk=stp(a)
elif " Boss: true" in a:
x.boss=True
elif " Plant: true" in a:
x.plant=True
elif " Looter: true" in a:
x.st+="Lot,"
elif " Assist: true" in a:
x.st+="Ass,"
elif " Aggressive: true" in a:
x.st+="Agr,"
elif " WalkMask:" in a:
x.walk=stp(a)
elif " Element:" in a:
tmp=stp(a)
tmp2=tmp.split(',')
try:
x.elem=int(tmp2[0])
x.elel=int(tmp2[1])
except:
print("Invalid Element for mob %s: %s" % (x.id, tmp))
exit(1)
elif " Race:" in a:
try:
x.race=int(stp(a))
except:
print("Invalid Race for mob %s: %s" % (x.id, a))
exit(1)
elif 'Drops: ' in a:
dropper=True
elif dropper and '}' in a:
dropper=False
elif dropper:
x.drops.append(stp(a).split(": "))
elif "Plant: true" in a:
skip=1
# Write last entry
MobAlloc(x)
writeMob()
wikib.write('\n\n|Mode|Desc|\n|----|----|\n')
wikib.write('|Lot|Looter|\n')
wikib.write('|Ass|Assist|\n')
wikib.write('|Agr|Aggressive|\n')
src.close()
def stp(x):
return x.replace('\n', '').replace('|', '').replace('(int, defaults to ', '').replace(')', '').replace('basic experience', '').replace('"','').replace(" ","").replace("\t","").replace('(string', '').replace('Name: ','').replace('Element: ','').replace('Race: ','').replace('AttackDelay: ', '').replace('WalkMask: ','').replace('MoveSpeed: ', '').replace('AttackRange: ', '').replace('ViewRange: ','').replace('Attack: ','').replace('ViewRange: ','').replace('Hp: ','').replace('Id: ','').replace('Lv: ','').replace('view range','').replace('attack range','').replace('move speed','').replace('health','').replace('(int','').replace('attack delay','atk.').replace('(','').replace(')','').replace('WALK_','').replace('Def: ','').replace('Mdef: ','')
def MonsterWrite(tbl):
global skipCI
# TODO: Check _mobs files to determine the usual monster density (a misc info to aid adding proper drop specs)
wikib.write("\n")
if stgen:
wikib.write("ID | Name | Mob Info | Stgen | Elegen | Misc Info | Rewards | Stats | Drops |
\n")
else:
wikib.write("ID | Name | Mob Info | Elegen | Misc Info | Rewards | Stats | Drops |
\n")
if not skipCI:
tbl=sorted(tbl, key=lambda tbl: int(tbl.mobpt))
for i in tbl:
if i.id == 'ID':
continue
# Special skips for REDESIGN
#if (int(i.id) < 1187):
# continue
if not bifs and ((int(i.hp) <= 50) or (i.race == 3)):
continue
if i.boss:
i.name=""+i.name+""
if stgen:
wikib.write('' +
i.id +" | "+
i.name +" | "+
mb_core(i) +" | "+
mbdt('advise',mb_stgen(i)) +" | "+
mb_eleg(i) +" | "+
mbdt('misc', mb_rdmisc(i)) +" | "+
mbdt('Exp\'s', mb_rdrw(i)) +" | "+
mbdt('stats', mb_rdstat(i)) +" | "+
mbdt('drops', mb_rddrop(i)) +" |
\n"
)
else:
wikib.write('' +
i.id +" | "+
i.name +" | "+
mb_core(i) +" | "+
mb_eleg(i) +" | "+
mbdt('misc', mb_rdmisc(i)) +" | "+
mbdt('Exp\'s', mb_rdrw(i)) +" | "+
mbdt('stats', mb_rdstat(i)) +" | "+
mbdt('drops', mb_rddrop(i)) +" |
\n"
)
wikib.write("
\n")
wikib.write("Total: %02d Monsters
\n" % len(tbl))
wikib.write("\n(↑) Return to top
\n\n")
def writeMob():
wikib.write(" ")
wikib.write("Lv 0-20
\n\n")
MonsterWrite(Mobs1)
wikib.write("Lv 21-40
\n\n")
MonsterWrite(Mobs2)
wikib.write("Lv 41-60
\n\n")
MonsterWrite(Mobs3)
wikib.write("Lv 61-80
\n\n")
MonsterWrite(Mobs4)
wikib.write("Lv 81-100
\n\n")
MonsterWrite(Mobs5)
wikib.write("Lv 101-150
\n\n")
MonsterWrite(Mobs6)
wikib.write("Lv 101+
\n\n")
MonsterWrite(MobsA)
def mbdt(summary, content):
return ""+summary+"
"+content+"
"
return "\
"+summary+"
\
"+content+"
"
def mb_core(mb):
buff=""
buff+="Lvl: %s
\n" % (mb.mobpt)
buff+="HP: %s
\n" % (mb.hp)
buff+="ATK: %s
\n" % (mb.atk)
buff+="DEF: %s/%s
\n" % (mb.dfn, mb.mdf)
if mb.st != "":
buff+="Modes: %s" % (mb.st)
return buff
def mb_eleg(mb):
buff=""
buff+="Race: %s
\n" % (WhatRace(mb))
if (mb.walk != "NORMAL"):
buff+="Walk: %s
\n" % (fwalk(mb.walk))
buff+="Element: %s
\n" % (WhatElem(mb))
return buff
############################################################
def mb_stgen(mb):
lv=int(mb.mobpt)
st=int(mb.str.replace(' ', '').replace('Str:',''))
#ag=int(mb.agi.replace(' ', '').replace('Agi:',''))
vi=int(mb.vit.replace(' ', '').replace('Vit:',''))
#it=int(mb.int.replace(' ', '').replace('Int:',''))
#dx=int(mb.dex.replace(' ', '').replace('Dex:',''))
#lk=int(mb.luk.replace(' ', '').replace('Luk:',''))
# Attack Range vs Attack Delay
ar=int(mb.range)
ad=int(mb.delay)
mv=int(mb.move)
magr=False
mass=False
if ('Agr' in mb.st):
magr=True
if ('Ass' in mb.st):
mass=True
if (not ar):
ar=1
# Over100 Special Formula
OVER100=0
if lv > 100:
OVER100=lv-100
lv=100+OVER100*0.25
# Fórmula da HPTable: 400+(x*50)
# MASTERED
lat=(lv*40+lv**1.2+lv*(st/100))
hat=(lv*40+lv**1.5+lv*(st/100))
if (ar > 1):
lat*=max(0.5, 1.0-((ar-1)/10.0))
hat*=max(0.5, 1.0-((ar-1)/10.0))
# Casos especiais
if mb.boss:
lat*=1.2
hat*=1.4
if "slime" in mb.name.lower():
lat*=0.3
hat*=0.3
# Attack is DPS. So we must adjust to be a damage per second
lat*=(ad/1872)
hat*=(ad/1872)
# Formula is not reliable
lat*=0.55
hat*=0.55
lat*=max(0.5, 1.0-(lv/10.0))
hat*=max(0.5, 1.0-(lv/10.0))
# Consider Aggressive and Assist mobs
if magr:
lat*=0.78
hat*=0.78
if mass:
lat*=0.89
hat*=0.89
# Over100 Special Formula
if OVER100:
lv=100+OVER100*2
# HP: Each 10 levels give you 100 extra weapon damage
# I expect from 6 to 14 hits to kill
# You deliver in average two hits per second
# Mobs deliver one hit each four seconds (and that's 1hand advantage)
lhp=lv*20*6+lv*(vi/100)
hhp=lv*20*14+lv*(vi/100)
if mb.boss:
lhp*=1.4
hhp*=1.8
if "slime" in mb.name.lower():
lhp*=0.6
hhp*=0.6
if ar > 1 and not OVER100:
lhp*=0.9
hhp*=0.9
# Experience is way too non-linear and I prefer to do it with reference formula
# like I was doing before
# But let's use a formula based on mobs-to-lvl-up where you must kill 1580 lv 60 mobs
# to raise from lv 60 (you can kill lv 50 mobs easier - but then I expect 3700 kills)
# mobs to kill to raise level
# This is the current mob-lvl-exp-table generated by this software
# As you see, it is somewhat similar to TMW Org.
# Remember aggressive, fast, and assistant mobs give even more exp
# Over100 Special Formula
if OVER100:
lv=100+OVER100
try:
calc_exp = max(1, int(math.floor(effective_hp * (math.sqrt(attack_factor) + math.sqrt(dodge_factor) + math.sqrt(persuit_factor) + 55)**3 * aggression_factor / 2000000)))
hxp=int(calc_exp*(1+lv/50.0))
lxp=int(calc_exp)
except:
hxp=1
lxp=1
print("Warning: Invalid exp for mob \033[1m%s\033[0m" % (mb.name.replace("", "").replace("", "")))
# Over100 Special Formula (kinda)
olv=0
if lv > 80:
olv=lv+0
lv=80+(lv-80)*0.25
# Defense follows the same player formula
dfn=((lv**1.255)*2.5)
dfn=dfn*350.0/810.0
mdf=max(0, lv-5)+(lv/10.0)
if not mb.boss:
mdf/=2
if ar > 3:
dfn/=2
# Nerf mobs def in 50% (to be more realistic to what we have)
dfn/=2
# Over100 Special Formula
if OVER100:
lv=100+OVER100
else:
lv=olv+0
del olv
# Force HP to be higher
# It'll only start applying from level 40 onwards
# It gives a bonus of 0.5% HP per mob level
# This means a level 100 mob got 60 times that stronger: 30%
if lv > 40:
lhp=lhp*(1.0+((lv-40)/210.0))
lhp=int(lhp)
# Norm
lhp=int(lhp)
hhp=int(hhp)
lat=int(lat)
hat=int(hat)
lxp=int(lxp)
hxp=int(hxp)
dfn=int(dfn)
mdf=int(mdf)
buff=""
buff+="HP Range: %s ~ %s
\n" % (lhp, hhp)
buff+="ATK Range: %s ~ %s
\n" % (lat, hat)
buff+="Maximum XP: %s ~ %s
\n" % (lxp, hxp)
buff+="DEF: %s / %s
\n" % (dfn, mdf)
buff+="Drop, Move, Elegen, aspd
\n"
buff+="
"
return buff
def mb_rdstat(mb):
buff=""
buff+="%s\n" % (mb.str)
buff+="%s\n" % (mb.agi)
buff+="%s\n" % (mb.vit)
buff+="%s\n" % (mb.int)
buff+="%s\n" % (mb.dex)
buff+="%s\n" % (mb.luk)
buff+="
"
return buff
def mb_rdmisc(mb):
buff=""
if "agr" in mb.st.lower():
buff+="View Range: %s\n" % (mb.view)
buff+="Attack Range: %s\n" % (mb.range)
buff+="AtkDelay: %s ms\n" % (mb.delay)
buff+="Move speed: %s ms\n" % (mb.move)
buff+="Element Level: %d\n" % (mb.elel)
return buff
def mb_rdrw(mb):
buff=""
buff+="%s\n" % (mb.xp.replace(' ', ' '))
buff+="%s\n" % (mb.jp.replace(' ', ' '))
try:
buff+="MobPoints: %d\n" % (int(mb.mobpt)*11/10)
except:
pass
return buff
def mb_rddrop(mb):
buff=""
for ax in mb.drops:
# Ignore disabled drops
if ax[0].startswith("//"):
continue
# Write drop
try:
buff+=ax[0]+': ' + str(int(ax[1])/100.0) + ' %\n'
except IndexError:
print("Fatal: invalid %s mob with %s drops" % (mb.name, str(ax)))
exit(1)
except:
print("[Warning] %s incorrect drop: %s" % (mb.name, str(ax)))
buff+=ax[0]+': ' + ax[1] + ' ppm\n'
# Save to SysDrops
SysDrops.append([ax[0], ax[1], mb.name])
return buff
showHeader()
testMobs()
wikib.write('
')
wikib.write("Run at: " + datetime.datetime.now().isoformat())
wikib.write('
')
wikib.write('
')
err=int(Ele[4])+int(Ele[8])+int(Ele[5])
wikib.write("""
Elemental Count
%s | %02d |
%s | %02d |
%s | %02d |
%s | %02d |
%s | %02d |
%s | %02d |
%s | %02d |
""" % (
"Neutral", Ele[0],
"Fire", Ele[3],
"Water", Ele[1],
"Nature", Ele[2],
"Holy", Ele[6],
"Dark", Ele[7],
"Error", err))
wikib.write('')
wikib.close()
#print(str(SysDrops))
# Aeros allocation
if aeros:
print("// These arrays are filled automatically by redesign.py");
print("setarray .ML_Plants, "+
str(Plants).replace('[','').replace(']','').replace("'","")+";")
print("setarray .ML_Boss, "+
str(Boss).replace('[','').replace(']','').replace("'","")+";")
print("setarray .ML_Aggr, "+
str(Aggressive).replace('[','').replace(']','').replace("'","")+";")
print("setarray .ML_Asst, "+
str(Assistant).replace('[','').replace(']','').replace("'","")+";")
print("setarray .ML_Loot, "+
str(Looter).replace('[','').replace(']','').replace("'","")+";")
print("setarray .ML_Lv50, "+
str(Level50).replace('[','').replace(']','').replace("'","").replace('ID, ','')+
";")
print("setarray .ML_Lv99, "+
str(Level100).replace('[','').replace(']','').replace("'","")+";")
showFooter()
exit(0)