1 #!/usr/bin/python3
2 # Christmas 2023 event generator (overwrites world/map/npc/annuals/xmas/2021.txt)
3
4 import sys, os, random, nanoid, xml, csv, time, traceback
5 from xml.dom import minidom
6 from xml.etree import ElementTree
7 from io import StringIO
8
9 # Argument: Path to tmwa server-data folder
10 GAME_PATH = sys.argv[1]; MAX_PRIZES=22
11
12 FILE = GAME_PATH+"/world/map/npc/annuals/xmas/2021.txt"
13 CLI = GAME_PATH+"/client-data/maps/"
14
15 # Retrieve list of maps
16 MAPS = os.listdir(CLI)
17 for m in list(MAPS):
18 MAPS.remove(m)
19 if m.endswith('.tmx'):
20 m=m.replace('.tmx','')
21 MAPS.append(m)
22 MAPS.remove('017-9'); MAPS.remove('botcheck') # Event maps are OK, but not those
23
24 # Some code stolen from testxml.py
25 def readAttrI(node, attr, dv):
26 return int(readAttr(node, attr, dv))
27
28 def readAttr(node, attr, dv):
29 try:
30 return node.attributes[attr].value
31 except:
32 traceback.print_exc()
33 return dv
34
35 ## Header with 2021 functions
36 HEADER="""// Christmas 2021-2023 Conversion Scripts
37 // This file was generated automatically.
38 // (C) The Mana World Team & Moubootaur Legends, 2021
39 // (C) The Mana World Team & Moubootaur Legends, 2023
40
41 function|script|ConvertChristmas21
42 {
43 return;
44 }
45 """
46
47 rewards = ["MovieCap", "BlueWolfHelmet", "CloverHat", "RabbitEars", "Goggles", "LeatherGoggles", "Crown", "Cap", "GuyFawkesMask", "WitchDoctorsMask", "ElfNightcap", "Sunglasses", "ChristmasTreeHat", "SantaBeardHat", "MoubooHead", "PaperBag", "BunchOfParsley", "SkullMask", "SnowGoggles", "HeartGlasses", "OperaMask", "JesterMask", "WitchHat", "GoblinMask", "ChefHat", "EskimoHat", "AFKCap", "SmileyCap", "RedShades", "GreenShades", "DarkBlueShades", "YellowShades", "LightBlueShades", "PinkShades", "BlackShades", "OrangeShades", "PurpleShades", "DarkGreenShades", "SnowLauncher"]
48 print("Generating Christmas 2023 event scripts... %d/%d rewards" % (MAX_PRIZES, len(rewards)))
49 with open(FILE, 'w') as f:
50 f.write(HEADER)
51
52 i=0
53 while i < MAX_PRIZES:
54 i+=1
55 myid = nanoid.generate("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", size=6)
56 myid = "%s%d" % (myid, i) # Obfuscation + Sequential
57
58 # Select the map
59 m = random.choice(MAPS)
60 MAPS.remove(m)
61 dom = minidom.parse("%s%s.tmx" % (CLI, m))
62 root = dom.documentElement
63 mapWidth = readAttrI(root, "width", 0)
64 mapHeight = readAttrI(root, "height", 0)
65 # Find the collision map so we can find random coordinates
66 layers = dom.getElementsByTagName("layer")
67 for layer in layers:
68 if readAttr(layer, "name", None).lower() == "collision":
69 collision = layer
70 break
71 # Dark magic below
72 collision = collision.getElementsByTagName("data")
73 for data in collision:
74 binData = data.childNodes[0].data.strip()
75 fo = StringIO(binData)
76 arr = list(csv.reader(fo, delimiter=',', quotechar='|'))
77 # arr contains the collision data, where only '0' is valid for us
78 # So now we do some brute-forcing within map margins
79 while True:
80 x = random.randint(20, mapWidth-20)
81 y = random.randint(20, mapHeight-20)
82 if int(arr[y][x]):
83 continue
84 break
85 ## And now, both m, x and y are set!
86
87 reward = random.choice(rewards) # What prize this disturbance gives
88 prize = random.randint(100000, 150000) # How much GP this disturbance requires
89 bf="""
90 %s,%d,%d,0|script|Disturbance#%s|176
91 {
92 if (gettime(6) < 11) goto L_Vanish;
93 if ($@MONEY_%s >= %d) goto L_Vanish;
94 set .@find, array_search(strcharinfo(0), $@XMAS23$);
95 if (.@find >= 0) goto L_NotYou;
96 goto L_Main;
97
98 L_Vanish:
99 message strcharinfo(0), "The disturbance mysteriously vanishes...";
100 disablenpc "Disturbance#%s";
101 end;
102
103 L_Broke:
104 mes l("You probably should sell your items first, so you have the specified amount of money in gold pieces.");
105 close;
106
107 L_NotYou:
108 mes l("I already closed a disturbance by myself, I should let others close the remaining ones.");
109 mes "##1"+l("WARNING: Closing them with alts still count as closing multiple disturbances and will affect negatively a future event.")+"##0";
110 close;
111
112 L_Excess:
113 mes l("That's too much money to throw on a whim. I shouldn't spend more than 200,000 GP at a time.");
114 close;
115
116 L_Close:
117 close;
118
119 L_Main:
120 mes l("This is a disturbance caused by Zax De'Kagen manipulations in the fabric of reality, using the powers acquired from absorbing the Ether Spirit a couple years ago. It seems intentional.");
121 mes l("It might be possible to seal it off by throwing ##Bmoney##b on it. Keeping it open might put the whole world at risk - or not - but closing multiple ones could be worse.");
122 mes "##9"+l("NOTE: Closing the disturbance will record your name in GM Logs. Closed disturbances will not re-open.")+"##0";
123 next;
124 mes l("Will you throw money at it?");
125 input @money;
126 if (@money < 1) goto L_Close;
127 if (Zeny < @money) goto L_Broke;
128 if (@money > 200000) goto L_Excess;
129 set Zeny, Zeny - @money;
130 getexp (@money * 3), (@money / 2);
131 set $MONEY_X23, $MONEY_X23 + @money;
132 set $@MONEY_%s, $@MONEY_%s + @money;
133 if ($@MONEY_%s >= %d) goto L_Reward;
134 mes l("The amount wasn't sufficient to close the disturbance, but it is now smaller. More money will need to be poured before it fully closes.");
135 close;
136
137 L_Reward:
138 getitem %s, 1;
139 set @i, getarraysize($@XMAS23$);
140 set $@XMAS23$[@i], strcharinfo(0);
141 set @i, 0;
142 gmlog "sealed a disturbance in map %s and found a %s behind.";
143 mes l("The disturbance shakes and vanishes - leaving a mysterious [@@"+%s+"|@@] in the place where the disturbance was...");
144 mes "##1"+l("WARNING: Closing them with alts still count as closing multiple disturbances and will affect negatively a future event.")+"##0";
145 disablenpc "Disturbance#%s";
146 close;
147 }
148 \n\n//===========================\n""" % (m, x, y, myid, myid, prize, myid, myid, myid, myid, prize, reward, m, reward, reward, myid)
149
150 rewards.remove(reward)
151 f.write(bf)
152
153 print("Generation complete.")
154