From The Mana World




Christmas 2023
Start date 12 December 2023
End date 31 December 2023
Reoccuring No


“These manaworldians are greedy, they always have been, and this can be used for my advantage. just wait and see ...” ─ Zax De'Kagen


During Christmas 2023, Zax De'Kagen began interfering with the world (with visible effects as early as December 1st). When the event started, he left behind several disturbances which would need to be sealed off with money.

There was 22 disturbances in 22 different maps, including maps normally inaccessible. Each disturbance required a different amount of money to be sealed and gave a different prize. The event was pre-generated randomly and with obfuscation on server startup, making very difficult or even impossible for TMW Staff (including those with root or physical access) to know precisely the disturbance locations or their rewards.

The event would produce a "negative" score of 1 point per unclosed disturbance, and 3 points per disturbance closed by someone's alt. A too high negative score could allow Zax De'Kagen to besiege the world again with a reduced experience rate (causing less experience to be earned overall until Zax is dealt with ─ if he's not dealt with, he might cause harm to the world, forcing several shops out of business or even destroying houses and sealing off areas). However, closing sufficient disturbances (without alts) could prevent the experience reduction, and even have the opposite effect on the experience rate.

The score was calculated manually based on GM Logs.

Generator Source Code

Spoiler warning: This section contains details that some people may not wish to learn. If this applies to you, take caution while reading this section (if you choose to read it at all).
  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 



Rewards

22 of the 39 Rewards which Chronos sells for 1 Boss Medal were featured. However, the precise selection was generated randomly.