tag:blogger.com,1999:blog-69583109580315474062024-02-21T15:11:25.496+01:00FOnline: 2238FOnline: 2238 Developers Bloglisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comBlogger78125tag:blogger.com,1999:blog-6958310958031547406.post-82522239417263661382013-07-02T21:23:00.002+02:002013-07-02T21:30:30.538+02:00FOnline: 2238 source released<br />
After shutting down the FOnline: 2238 server an issue remained bothering the developers of the game: what to do with the code, tools and the custom created content?<br />
<br />
The discussion was led, the opinions were presented, the decision was made; after a while we did realise that there is no point in letting the creation hibernate under the virtual layer of dust, hence the release of the source code, which includes above mentioned tools and content, was the only rational way to go. There were various reasons for such decision, but one of them seemed to have an additional, quite heavy impact on the decision itself - it was the fact, that our new endeavours would soon be concentrated on a better promotion of the FOnline engine via our new English speaking developer community <a href="http://fodev.net/">fodev.net</a>, on continuing its development into a platform not only for Fallout-like games, but also RPGs in general, and last but not least, to cooperate with everyone who had these (or similar) aims in regard to the engine. It occurred to us as the next logical step in providing the support for many indie projects based on FOnline, as well as in attracting potential developers to start understanding its capabilities and limitations.<br />
<br />
We do hope that everyone interested in the FOnline engine will benefit from the release of the FOnline: 2238 source.<br />
<br />
<a href="https://github.com/rotators/fo2238" target="_blank">Server and related tools</a><br />
<br />
<a href="http://fo2238.fodev.net/source/FOnline2238.client+mapper.zip" target="_blank">Client + Mapper</a><br />
<br />
More tools (with sources)<br />
<a href="https://github.com/rotators/tools">https://github.com/rotators/tools</a><br />
<a href="https://github.com/rotators/WitchLord">https://github.com/rotators/WitchLord</a><br />
<a href="https://github.com/wipe2238/FOUpdaterEx">https://github.com/wipe2238/FOUpdaterEx</a><br />
<br />
<a href="http://fo2238.fodev.net/source/" target="_blank">Alternative Download</a> <span style="font-size: x-small;">(these files will not be updated any longer, and further changes on github won't be included here)</span><br />
<br />
<a href="http://fo2238.fodev.net/source/docs/" target="_blank">Autogenerated documentation</a> (using Doxygen) <span style="font-size: x-small;">(the Reference section is the most interesting there)</span><br />
<br />
For those who cannot wait to run their own server and look around from a whole new perspective, we prepared a <a href="http://fodev.net/forum/index.php/topic,29382.0.html" target="_blank">short tutorial</a> which should help you with the first steps as an authenticated character in the game world. Have fun!lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-49365201130152970452013-06-28T22:29:00.001+02:002013-06-28T22:37:43.114+02:00fodev.net <div style="text-align: justify;">
<br />
After shutting down the FOnline: 2238 Server, we have moved the database (as
well as some other data) to a new place - fodev.net forum!</div>
<div style="text-align: justify;">
<br />
Why
such a forum? The FOnline scene always lacked a community website in
English dedicated to its developers. Informations about SDK are scarce and
scattered - we see it as an obstacle in achieving more interest into
FOnline. That's why the former FOnline: 2238 devs decided to fill this gap
by creating fodev.net. We would like it to become a place for all people
focused on creative aspects of the FOnline engine - place where you can discuss
developing games, share ideas, ask questions, publish and share your
work with others and announce new FOnline projects. A friendly hub for
English speaking developers.<br />
<br />
If you were registered on the
FOnline: 2238 forum you don't have to create a new account - you can use
the same login and password.<br />
<br />
Of course fodev.net is not the only
thing that we would like to attend to. We are still eager to create games. Currently, however, we have decided to focus on something
slightly different than a game - creation of a new FOnline development kit
with improved scripts and tools. Our aim is to get a better starting
point for new projects. Only time will tell if we will be successful.
This new endeavor is in very early stage and you can follow it on <a class="bbc_link" href="https://github.com/rotators/fosdk" target="_blank">Github</a>. It's developed as open source.<br />
<br />
Speaking
about open source... we have more news for you. To answer to many
requests of the FOnline: 2238 fans, we have decided to release the source
code of our game. We need to clean it up a bit and as <i>soon</i> as
this work is done, everyone should be able to download the sources,
start a FOnline: 2238 server or create their own projects based on it.
This shouldn't take too long, so stay tuned!<br />
<br />
Kind regards,<br />
Rotators </div>
lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-51604748723607415182013-06-06T19:16:00.003+02:002013-06-06T19:17:28.625+02:00The End<p><b>The Server</b><br/>
On Sunday, June 23rd 2013, the FOnline: 2238 server will be shut down. The details are trivial and pointless, the reasons, as always, purely human ones. The interest in the current state of the game is steadily declining, while, at the same time, expectations about further development of the project can't be matched. With a decimated player base and, in turn, low number of donations we can't afford us any further server lease.</p>
<p>For you, the players, this means that there are over two weeks to play and discuss FOnline: 2238 on the current forum. This moment occurs important to many of us and we would like to use this opportunity to invite all the people who have been sharing so many memories from the last four years to login on June 23rd (evening time CET) for a big shutdown party. We definitely would love to have as many people as possible during these very moments and, of course, during the last days of 2238.</p>
<p>We would like to thank the players who supported us with their suggestions, bug reports, patches and content. Special thanks to all wiki editors, moderators and game masters. Last but not least, we would like to thank all our donors, who helped us to keep the 2238 server running for so long! Without you, the last 4 years wouldn't have been the same.</p>
<p>However, this is not the end, merely a new beginning for us - and we hope for some of you as well.</p>
<p><b>The Forum</b><br/>
The current 2238 forum will be transformed into a "FOnline developer zone" - a community dedicated to game development using the FOnline engine, a place where you can discuss developing games, ask questions about the engine, publish and share your work with others, announce new FOnline projects and maybe even have a whole board for your own project. As an inevitable result of this change, all 2238 game related boards will be closed with everything regarding the "SDK" and "3D Development" expanded. If you have any ideas and suggestions, feel free to share them with all of us.</p>
<p>Currently there is no decision whether the FOnline: 2238 sources will be released or not.</p>
<p>That's all folks, have fun in the last days of 2238!</p>
<br />
<div style="text-align: center;">
<a href="http://4.bp.blogspot.com/-qL7p7VEBnrQ/UbDD6WvZW5I/AAAAAAAAAGQ/vG55V1IOXZc/s1600/the-end.png" imageanchor="1"><img border="0" src="http://4.bp.blogspot.com/-qL7p7VEBnrQ/UbDD6WvZW5I/AAAAAAAAAGQ/vG55V1IOXZc/s320/the-end.png" /></a></div>
Lexxhttp://www.blogger.com/profile/17916128545723869796noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-11770187345344673872012-03-03T16:03:00.005+01:002012-03-03T16:09:40.352+01:00Everything has its end, so does the Great Exodus<div style="text-align: justify;">We have officially exited the hyperspace and reached the point where everything is safely moved to Berlin. Please consider using the new coordinates for locating the Mothership:<br /><br />IP: <span style="color: rgb(204, 0, 0); font-weight: bold;">176.9.105.171</span><br />Port: <span style="color: rgb(204, 0, 0); font-weight: bold;">2238</span><br /><br />Hopefully, all ships will rally at the given coordinates soon. If you have any problems, use the standard procedure and contact the <a href="http://fonline2238.net/forum/index.php">Fleet Network</a>.</div>lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-2707085337714584512012-03-01T21:33:00.007+01:002012-03-01T21:43:18.067+01:00Exodus in progress...<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjigrQzm83rR5zFJKIkF7dh4WspcjiXQy-jwlqT89hLcqAMrzMRiwkEaBFafVOipwHt52cm0NiRZqvwlDBPUToEhm9F87SmFbla4G4NHLtXpuodXJKVDDtQwmgibYWrUAIMpSS4d-IW6SM/s1600/Exodus.gif"><img style="float:left; margin:10px 8px 8px 0;cursor:pointer; cursor:hand;width: 320px; height: 160px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjigrQzm83rR5zFJKIkF7dh4WspcjiXQy-jwlqT89hLcqAMrzMRiwkEaBFafVOipwHt52cm0NiRZqvwlDBPUToEhm9F87SmFbla4G4NHLtXpuodXJKVDDtQwmgibYWrUAIMpSS4d-IW6SM/s320/Exodus.gif" alt="" id="BLOGGER_PHOTO_ID_5715031595618740242" border="0" /></a><div style="text-align: justify;"><span style="font-style: italic;">Exodus, movement of Jah data.</span> As you read these lines, one after another services will leave the Paris nebula. The hyper-drive has been charged and almost ready for start, there is no turning back. Our destination, Berlin system with a G-class star and a habitable world for at least few generations. New space coordinates for all incoming ships will be given soon, stand by. According to calculations provided by the Fleet Command there are no electromagnetic anomalies in the hyperspace we are going to pass through, which effectively means that the fleet data and support systems shall reach the destination point unharmed.<br /><br />All sections reported in. Expect the hyperspace drive to be triggered any minute now. </div>lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-17710612370027210272012-03-01T14:09:00.003+01:002012-03-01T14:13:03.858+01:00ExodusWe're starting the process of moving the data from current server to new one. For now, everything will run normally, but later this day, we'll have to shut the website.<br /><br />Game server will be closed later (after we will finish to move website), so that game itself should be available as long as possible (and with shortest possible interruption). More info will be announced as we go.<br /><br />We hope everything will go smooth and without bigger problems!scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-81856426732401281482012-02-28T07:37:00.001+01:002012-02-28T07:38:48.176+01:00Server issues - update #4We're almost there, FOnline server seems to be running OK, but we still have troubles with database. Not really about the data itself - the MySQL server process is crashing occasionaly.scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-58437640636615415732012-02-27T22:34:00.004+01:002012-02-27T22:53:46.648+01:00Shelter<div style="text-align: justify;">It may be no vault, but it'll keep your head safe from the surrounding fallout and your lungs free of radioactive particles. Just head <a href="http://www.moddb.com/games/fonline-2238/forum">over here</a> or maybe <a href="http://www.indiedb.com/games/fonline-2238/forum">over there</a> and start posting on our temporary forum. If for some reason you do NOT want to click any of those two links because you are incredibly suspicious about underlined words on the network, then you can type one of these super secret codes in your browser window and gain direct access to our forum mask in no time! Here are the codes:<br /><br /><a href="http://www.moddb.com/games/fonline-2238/forum">http://www.moddb.com/games/fonline-2238/forum</a><br /><a href="http://www.indiedb.com/games/fonline-2238/forum">http://www.indiedb.com/games/fonline-2238/forum</a><br /><br />You'll need ModDB/IndieDB/Desura account in order to start posting, so be sure to register (if not already). See you over there!<br /></div>lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-73513157112308291002012-02-27T16:08:00.003+01:002012-02-27T16:09:40.075+01:00Server issues - update #3Some good news finally. We've managed to restore the OS, but it still requires some configuration. So, everything is on its way to be fully functioning once again.<br /><br />Again, thanks for your patience and asking for a bit more patience, the rush on server could make things a bit harder for us:)scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-61442429973315015372012-02-26T20:42:00.012+01:002012-02-26T21:21:22.099+01:00Server status - in the meantime...<div style="text-align: justify;">The server is down for four days due to an attack and inability to manage the server remotely in a proper way. Considering the circumstances, the question emerges: what can or should be done?</div><br /><div style="text-align: justify;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuOgxxcZVd9CEIN3m9QGoXwTGhyphenhyphen_V-_w8SYXn8Z4LQyXU0lg4RkRvYeeV9HKiw_eiEsmXjAGLceYjCSXsBpj2K-Mixqeuz8Azr6HRon4viXpzDJVELiX95k7shC7N-BDrCjAXsKN6ARLY/s1600/VaultNetOverload_small.jpg"><img style="float:right; margin:10px 10px 10px 10px;cursor:pointer; cursor:hand;width: 176px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuOgxxcZVd9CEIN3m9QGoXwTGhyphenhyphen_V-_w8SYXn8Z4LQyXU0lg4RkRvYeeV9HKiw_eiEsmXjAGLceYjCSXsBpj2K-Mixqeuz8Azr6HRon4viXpzDJVELiX95k7shC7N-BDrCjAXsKN6ARLY/s200/VaultNetOverload_small.jpg" alt="" id="BLOGGER_PHOTO_ID_5713533973394938002" border="0" /></a>After the initial problems we've had, the server could have been pinged most of the time and the the low-level host control panels were responding, but booting up the old chap with primary OS was impossible. The technical support was (probably) doing their best to answer to our requests, but the communication was tedious, usually taking too long or resulting in additional waiting intervals. In other words, remote management proved to be quite difficult, which we were counting on, since we're talking about one of the cheapest hosting solutions among those offered by our host. Good news is that the game session and other critical data look good thus far. We will continue working on getting everything back up, but no rational estimations can be given at this point.<br /><br />I'd dare to say that this is not a case without solution and we have been considering several options for quite a time already. Long story short - one of the most intriguing ideas right now is to move to another host, i.e. to find better offer for beans, getting... ehmmm... more (and better) beans in the process. Currently, our critical control point proved to be the above mentioned server management, which provides us with next to none flexibility when it comes to keeping the server up n' running. As all of us already know, a proper server management is necessity for projects like 2238, no more, no less. According to some unofficial feedback, there's a good portion of players and the community interested in backing up such kind of "exodus", which is a good sign and a wind in the back for meeting the final decision. We would like to use this opportunity to thank everyone for supporting us during all good and hard times we've been through together.<br /><br />Posting comments is temporarily available. Please don't spam, thank you.<br /></div>lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-6941053321738427012012-02-25T20:17:00.001+01:002012-02-25T20:19:33.971+01:00Server issues - update #2Short update. Server was (finally!) unlocked yesterday, but it looks like during the "emergency shutdown," performed by our kind provider, the operating system image got damaged and - in the time of writing this note - we are still unable to boot it.<br /><br />In other news - current situation brings back the latest discussion about changing server, which had been planned for the next few months. We are currently checking various offers and will pick one <s>soon</s> shortly. This should solve some of the issues which we have experienced already - current server machine configuration is a few years old and it hasn't changed much during that time.<br /><br />In the meantime, we're preparing the various elements, which we have access to, for migration. Once again thanks for patience!Lexxhttp://www.blogger.com/profile/17916128545723869796noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-83545515755157501072012-02-24T11:32:00.003+01:002012-02-24T11:39:52.971+01:00Server issues - updateTime for some more news - apparently, it was an attack. Our line got saturated with almost constant 80M transfer, and our provider policy is...to lock such server if it's under attack. Yes, we know it's crazy, it's like they're actually helping the attacker by doing that (when such server is locked, we can't do a thing on it as well).<br /><br />The problem is, that they've said they're gonna take off the lock in few hours, but they didn't do it till now. Even if they stated so in tech support request message.<br /><br />So we're still waiting...scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-15200493587895170752012-02-22T11:56:00.002+01:002012-02-22T11:59:46.640+01:00Server issuesWe've encountered strange server machine behavior lately (you've probably noticed it as well - as the resulting lags). We were investigating the issue and, as a part of investigation, we performed machine hard reboot.<br /><br />The machine ceased to reply after that. We currently do not know more details about the issue, we'll try to keep you up to date with information.<br /><br />Thanks for your patience.scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-37906486369422498682012-01-04T22:38:00.000+01:002012-01-04T22:42:54.864+01:00Server forecast for Friday 06-Jan-2012: Wipe with heavy data loss<div style="text-align: justify;">COMMERCIAL: This server forecast is brought to you by "Rotators", the world leader in donation beggings & solutions!<br /></div><br /><div style="text-align: justify;">FORECAST: on Friday, 6th of January, we may count with some heavy wipe followed by data loss on global scale. The wipe will most probably pass the Central and Western Europe at 09:00 PM (21:00), causing complete data loss for everyone. The unstable post-wipe conditions may last for the next two days, but we believe there will be no unexpected data loss during that time.<br /></div><br /><div style="text-align: justify;">We advise you to gear up properly, including protective suits and equipment, take a lot of meds with you and go out. There's no need not to enjoy the next few days until the wipe hits us.<br /></div><br />Don't forget to let the Brahmin out and burn down your accomodation!<br /><br />Until our next forecast - have a nice time!lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-71693386767690874912011-12-23T17:51:00.005+01:002011-12-23T18:35:52.015+01:00Please read: a personal appeal from the FOnline: 2238 Developer Lisac Twok<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1rDTjWcd2GOiFT9BSCsFKjo3oVD95nKEs9m-MiHO-hSxmZ0EI-bWlUkqf9M7Ctlj7gOGGOwmaR0XSF4vrzia3ZTgUkuQJPIOJiPAjeQbKkNSKyt-zKnfo42DyPeuYMCnRVPGtcwZPz60/s1600/test.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 200px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1rDTjWcd2GOiFT9BSCsFKjo3oVD95nKEs9m-MiHO-hSxmZ0EI-bWlUkqf9M7Ctlj7gOGGOwmaR0XSF4vrzia3ZTgUkuQJPIOJiPAjeQbKkNSKyt-zKnfo42DyPeuYMCnRVPGtcwZPz60/s200/test.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5689374926490934738" /></a><br />I feel like living the last of my days in a polluted, unclean world, infested with bugs, glitches and crashes.<br /><br />Imagine the world where every human being on the planet could wipe their virtual reality as they want, whenever they want. Although we're currently far away from that vision, it doesn't mean we have to give up on our dreams! Wipe, my brothers and sisters, wipe is our reality.<br /><br />With the latest features being tested and discussed, with those last bugs being squashed or pulverised with pulse rifle, with each and every stinking breath of the stuck NPCs that break your quests - we are one step closer to our vision! As I write these words, the critical amount of bits and bytes are being accumulated in Paris, waiting to come into contact with a void function that shall release the Big Bang and bring us the next wipe, the salvation! The last of our days are coming, beware!<br /><br />Not soon, but very soon, the wipe will bring the end of days as we know it and bring a new life to the brave world that has to be born on the ashes of the old one. Very soon.<br /><br />Gather your forces. Wake up your sleeping moles. Send a telegram to Rangers. Take your minigun from the rusty tribal chest. Get your granpa stand by your side. Because, very, very soon...<br /><br /><span style="font-weight:bold;">It's about to wipe.</span>lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-40827770372327562022011-11-19T10:55:00.002+01:002011-11-19T11:30:29.589+01:00Wanted: Donations - Dead or Alive<div style="text-align: justify;">Like the title says, we are in dire need of donations for keeping the server running for the upcoming three months. The deadline is 6th December and the server fundings are currently short for ~80 €. Any amount is greatly appreciated and shall help the developers to concentrate on finishing the new features for the upcoming wipe, rather than to see their efforts being hampered by failing to oblige a quarter invoice for the server maintenance.<br /><br />As always, Cvet & Rotators are grateful for your donations, as well as for any other great work and feedback provided by the FOnline communities.<br /></div>lisac2khttp://www.blogger.com/profile/13042892623147931517noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-9935199586959211502011-10-14T13:37:00.000+02:002011-10-14T15:39:08.025+02:00Improving the vintageOur old and rusty faction terminal, the very first feature of 2238 could use some improvements. While we started the project few years ago, we couldn't hope for client scripts, nor for easy scripting client-side GUI. The only possibility was to use existing dialog functionalities.<br /><br />Over the time it became obvious, that using such 'interface' is really big pain, and the usability of our game is not up to modern standards by a mile or two. But we have left that part unchanged, as there were other things to do (and still are). The upcoming wipe will not change it drastically, but it will surely improve the way factions are managed.<br /><br /><h3>Multiple bases</h3><h3><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWM0efSkTh2fF5BF8N9VrJd7DSjYJG-LV9UOMdrBoaZGIZrnv5zlA3DqnMe-V91xd1nRFxyZZOtR964KKX8ZjA9vGurCeXBXahyBlbJcGGZKvyJefD_d1GpzuAuorTtqX08tGbFX12Ctp6/s1600/screen.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 191px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWM0efSkTh2fF5BF8N9VrJd7DSjYJG-LV9UOMdrBoaZGIZrnv5zlA3DqnMe-V91xd1nRFxyZZOtR964KKX8ZjA9vGurCeXBXahyBlbJcGGZKvyJefD_d1GpzuAuorTtqX08tGbFX12Ctp6/s320/screen.jpg" alt="" id="BLOGGER_PHOTO_ID_5663097594459993922" border="0" /></a></h3> There were so many factions created since the last wipe that we've run out of names. Several times. Most of them were only created to alleviate one problem - a faction is meant to have only one base, and all members have access to it, some were created when factions were creating an alliance, and some only because some rich player wanted private workbench and whores.<br /><br />Of course, not all problems will be fixed immediately, but we're going to allow one faction to own multiple hideouts.<br /><h3>Access</h3><br />To simplify things, the access will be resolved on per-rank and per-status basis. This means it won't be possible to do it per-player and per-faction (not yet at least), but still, that simple modification should make life of faction leaders much, much easier, and it should increase the importance of proper rank assignment.<br /><br /><h3>Easier status change</h3><br />We've also added the ability to create list of known factions. Such list is then used to alter the status of all members of said faction at once.It should work well with per-status access for bases. While we still have basic statuses: friend/neutral/enemy/invited, the system should allow for more so we could improve one that. Question is, whether it's gonna be worked on, or whether web-faction-terminal will takeover.<br /><br /><h3>Summary</h3><br />So why we haven't reworked it from the scratch, possibly creating client-side interface for it? Well, there are few reasons. First of course are, the plans for web faction terminal (which does not rule out easy client-side interface alongside with web interface). The other is that we're still working on GUI internals, our GUI code differs from the default SDK, and we're working on a ways to make them compatible. Sometimes it's good to work on internals first.scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-68191930265585646342011-09-14T00:21:00.000+02:002011-09-14T12:25:34.767+02:00Living without client<a href="http://fonline2238.blogspot.com/2011/09/testing-your-server-scripts.html">Recently</a> I've written about the possibility to test the features by writing code, that test them. As I've started doing some refactoring in our code, I immediately wanted to use that feature. Let's see how it worked out...<br /><br /><h3>Gathering, once again</h3><br />As we already mentioned, we are changing (again) the way resources will be gathered. This of course required yet-another-refactor of that part of code. Last time I was doing this, it was rather boring task: refactor script of one facility, start server, login, spawn some of those facilities, spawn myself needed item, use it, check. Rinse and repeat (sometimes you may hot-reload the script, but you need to still test it manually).<br /><br />This time I decided to write set of test cases for every 'use event' that results in resource being acquired. And then, those tests will check the stuff for me, so that I do not even need to fire up the client. Nifty!<br /><br /><pre class="brush: as">class Plant : IFacility<br />{<br /> Item@ item;<br /> uint16 resource;<br /> uint batch;<br /><br /> Plant(Item& item, uin16 resource, uint batch)<br /> {<br /> item.SetEvent(ITEM_EVENT_USE_ON_ME, "_UseItem");<br /> item.SetEvent(ITEM_EVENT_SKILL, "_UseSkill");<br /> item.SetEvent(ITEM_EVENT_FINISH, "_Finish");<br /><br /> @this.item = item;<br /> this.batch = batch;<br /> this.resource = resource;<br /> this.batch = batch;<br /> }<br /><br /> int get_Amount() { return item.Val1; }<br /> void set_Amount(int val) { item.Val1 = val; }<br /><br /> bool UseItem(Critter& cr, Item& usedItem)<br /> {<br /> uint pid = usedItem.GetProtoId();<br /> if(pid == PID_KNIFE || pid == PID_COMBAT_KNIFE || pid == PID_LIL_JESUS_WEAPON || pid == PID_THROWING_KNIFE)<br /> {<br /> if(IsOverweighted(cr))<br /> {<br /> cr.SayMsg(SAY_NETMSG, TEXTMSG_GAME, STR_OVERWEIGHT);<br /> return true;<br /> }<br /> else<br /> {<br /> cr.AddItem(resource, batch);<br /> cr.SayMsg(SAY_NETMSG, TEXTMSG_TEXT, text);<br /> }<br /> return true;<br /> }<br /> else return false;<br /> }<br />}<br /></pre><br /><br />This is an excerpt from a class that represents plant facility that we may cut with knife to use the needed resources. It's represented in-game as an item, and we are initializing it in item script in following way:<br /><pre class="brush: as">void item_init(Item& item, bool firstTime)<br />{<br /> AddFacility(item, Plant(item, PID_FIBER, 2));<br />}<br /></pre><br />It uses the pattern I've described in <a href="http://fonline2238.blogspot.com/2011/08/what-would-be-point-of-having.html">following </a>post. The Plant constructor assigns the events to the item:<br /><pre class="brush: as">// critter uses skill on facility<br />bool _UseSkill(Item& item, Critter& cr, int skill)<br />{<br /> IFacility@ facility = GetFacility(item);<br /> if(valid(facility)) return facility.UseSkill(cr, skill);<br /> return false;<br />}<br />// critter uses item on facility<br />bool _UseItem(Item& item, Critter& cr, Item@ usedItem)<br />{<br /> if(valid(usedItem))<br /> {<br /> IFacility@ facility = GetFacility(item);<br /> if(valid(facility)) return facility.UseItem(cr, usedItem);<br /> }<br /> return false;<br />}<br />// :><br />void _Finish(Item& item, bool deleted)<br />{<br /> IFacility@ facility = GetFacility(item);<br /> if(valid(facility)) RemoveFacility(item);<br />}<br /></pre><br /><br /><h3>Testing stuff</h3><br />First thing we would probably like to test is, whether critter really receives the items if using proper tool on that facility.Let's first prepare a helper that's gonna greatly reduce the amount of repeated code:<br /><pre class="brush: as">void mock_CritterAddItem(Critter& cr, uint16 pid, uint count)<br />{<br /> CallExpectation("CritterAddItem_" + pid + "_" + count);<br />}<br /><br />class Fixture<br />{<br /> Critter@ cr;<br /> Item@ tool;<br /> Item@ item;<br /><br /> Fixture(uint16 tool, string@ script)<br /> {<br /> @cr = MockCritter(1);<br /> Mock("Critter::AddItem", "mock_CritterAddItem");<br /> @this.tool = MockItem(tool);<br /> @this.item = MockItem(1); // this pid does not matter<br /> item.SetScript(script);<br /> }<br /><br /> IFacility@ get_Facility() { return GetFacility(item); }<br />}<br /></pre><br />And now, the first test:<br /><pre class="brush: as">void test_PlantFruitGivesProperResource()<br />{<br /> Fixture fix(PID_KNIFE, "prod_plant_fiber@item_init");<br /> fix.Facility.Amount = 100;<br /> ExpectOnce("CritterAddItem_" + PID_FIBER + "_" + 2);<br /><br /> fix.item.EventUseOnMe(fix.cr, fix.tool);<br /><br /> VerifyExpectations();<br />}<br /></pre><br />The most important part is the mock for <span style="font-family: courier new;">Critter::AddItem</span>, which just stores the expectation named in following manner: <span style="font-family: courier new;">CritterAddItem_PID_COUNT</span>. In above test function, we're preparing a context consisting of:<br /><ul><li>critter</li><li>knife</li><li>item representing the plant</li></ul>We invoke the event that would be normally invoked when player or npc would use a knife on the plant (<span style="font-family: courier new;">EventUseOnMe</span>). The call process normal behavior, but when it encounters call to <span style="font-family: courier new;">Critter:AddItem</span> it calls our mock instead. So we expect that our function is gonna call this function with <span style="font-family: courier new;">PID_FIBER</span> and <span style="font-family: courier new;">2</span> as parameters. Looking into the code of <span style="font-family: courier new;">Plant::UseItem</span>, we see that indeed it should work. <span style="font-family: courier new;">Plant::batch</span> is equal to 2, so it's gonna execute exactly what we wanted. Running the test confirms that. Yay!<br /><br /><h3>Code is already bugged</h3><br />Oh great:( I'm writing some code snippets that already contain bugs. So what? Let's fix them, but first let's write a test that confirms them, check if it fails, and then fix the bug and re-check the test:<br /><pre class="brush: as">void test_PlantFruitEmpty()<br />{<br /> Fixture fix(PID_KNIFE, "prod_plant_fiber@item_init");<br /> fix.Facility.Amount = 0;<br /> ExpectNonce("CritterAddItem_" + PID_FIBER);<br /><br /> fix.item.EventSkill(fix.cr, -1);<br /><br /> VerifyExpectations();<br />}<br /></pre><br />Since there are gonna be no timeouts on characters, the facilities itself should somehow limit the amount of resources that we can obtain (and they will be regenerated over time). We've already had implemented some <span style="font-family: courier new;">Amount</span> property in our <span style="font-family: courier new;">Plant </span>class, but the code didn't care about it. And this test proves that - we expect that <span style="font-family: courier new;">AddItem</span> is not gonna be called with <span style="font-family: courier new;">PID_FIBER</span> (and whatever count) at all, but it is, because we do not check for amount left in <span style="font-family: courier new;">UseItem</span> method. Let's fix it:<br /><br /><pre class="brush: as"> // ...<br /> else<br /> {<br /> if(this.Amount > 0)<br /> {<br /> cr.AddItem(resource, batch);<br /> cr.SayMsg(SAY_NETMSG, TEXTMSG_TEXT, text);<br /> }<br /> }<br /> // ...<br /></pre><br />And let's run the test again. It succeeds! We've fixed a bug, we tested it. Without doing manual labor.<br /><br />The method I'm describing here, really makes me happy developer, as I do not need to spend time on manual testing, and also I am preparing more and more tests that's gonna test whether existing features are still working in the future (you know, some change in other area of code can really break the other part of code, in an unexpected way, better catch at least some of such bugs).<br /><br />There is still one bug left in the code (probably much more, I wrote this post without compiler). I'm leaving it for readers to find out and write test code that proves it!scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-4812375940755403392011-09-09T11:04:00.017+02:002011-09-09T11:45:22.775+02:00Walking the way of bombs<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-mPsHv4vG91Y/TmnZgkBdPfI/AAAAAAAAAE0/qAvenoEx40w/s1600/CrazyBomber.gif"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 91px; height: 127px;" src="http://3.bp.blogspot.com/-mPsHv4vG91Y/TmnZgkBdPfI/AAAAAAAAAE0/qAvenoEx40w/s400/CrazyBomber.gif" alt="" id="BLOGGER_PHOTO_ID_5650286360961826290" border="0" /></a>It isn't exactly news that various towns in the wasteland are racked with the crazy nature of suicide bombers. In real life and in FOnline: 2238. This particular way of entertainment is in almost all cases only entertaining for a very small minority of people and due to this, discussions internally and <a href="http://fonline2238.net/forum/index.php?topic=18336.0">externally</a> are kind of an unofficial sporting event by now.<br /><br />Anyway, to make it short: In the future we want to change the way explosives are working and used in the game, to limit (but not totally remove) the amount of suicide runs. To accomplish this, we internally agreed on the following ideas:<br /><ul><li>Guards can "sniff" activated explosives (C4, Dynamite, and placed Landmines).</li><li>The player <span style="font-weight: bold;">can't</span> activate explosives (C4, Dynamite, and placing Landmines) whenever a guard is around or if the player is in guard range. (It simply doesn't work, with an entry in the message log.)</li><li>The player <span style="font-weight: bold;">can</span> activate explosives (C4, Dynamite and placing Landmines on the ground) whenever a guard is <span style="font-weight: bold;">not</span> around or if the player is <span style="font-weight: bold;">not</span> in guard range.</li></ul>The few rules should result in harder suicide runs, without making them impossible, because the players action now will be as the following:<br /><ol><li>The PC has to activate the explosives <span style="font-style: italic;">far away</span> from guard NPCs,</li><li>then the PC has to run up to their targets (NCR bazaar....),</li><li>and then <span style="font-style: italic;">hopefully</span> explode there before a guard can kill the bomber and take the explosives (disarming them).</li></ol>These simple changes should make the game better than with the current system (guard NPCs attack players with active explosives), which is pretty much useless, as especially dynamite can be triggered in around one second and nobody is able to react on it this fast.<br /><br />Of course, the subject is still open for discussion and suggestions in the forum (for example, there were ideas about involving demolition expert profession in this process). Also we aren't quite sure about when exactly we are able to take this over into the game. It probably won't happen before the next wipe, but I'll hope we can push it afterwards.Lexxhttp://www.blogger.com/profile/17916128545723869796noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-6823493534490184132011-09-06T13:37:00.006+02:002011-09-06T21:24:24.634+02:00Testing your server scriptsI've finally gotten around the way to unit test FOnline scripts. I'm happy about it, though it's still a prototype, and I've only written few tests just to try it out.<br /><br />Why test your code and why should we care? Well, it's very popular approach nowadays, to write a code that tests your code, just to be sure everything works as expected (and it's hard to keep track of everything with large codebase). It's common misconception that's not feasible for games, after all, how can you test something as complex as interactive program - and even worse - with multiple players?<br /><br />The answer is that you're not writing a code to test your whole game. You're not gonna write a bot that plays it, does all the quests and check if everything still works (and even looks for holes in the walls, though that'd actually be cool). You write the code to test only portions of it, the small pieces, the units. Hence the name: <a href="http://en.wikipedia.org/wiki/Unit_testing">unit testing</a>. There's of course lots of materials on it over the internet, so we're not going to dive into the details, I just describe what were the problems, and how we solved it, so that we can finally try unit tests.<br /><br /><h3>Isolating the code</h3><br />When you look at the code you'd like to test, first thing that come to mind is that it's probably too complex and involves too much dependencies to be easily testable. Let's look at the example:<br /><pre class="brush: as">bool critter_use_item(Critter& cr, Item& item, Critter@ targetCr, Item@ targetItem, Scenery@ targetScen, uint param)<br />{<br /> bool isPlayer=cr.IsPlayer();<br /> bool useOnSelf=(not valid(targetCr) && not valid(targetItem) && not valid(targetScen))<br /><br /> // Radio<br /> if(FLAG(item.Flags,ITEM_RADIO) && useOnSelf)<br /> {<br /> if(isPlayer) EditRadioSettings(cr,item);<br /> return true;<br /> }<br /> // ...<br />}<br /></pre><br />Say we need to test that when player uses the radio item, he can edit its settings. Unfortunately, to invoke <span style="font-family:courier new;">critter_use_skill</span> int this context we need:<br /><ul><li>player</li><li>item</li></ul>OK, we may easily spawn radio, but how can we spawn player? Scripting subsystem does not allow us to do this. We could just spawn critter, but then we see script cares about <span style="font-family:courier new;">IsPlayer()</span> condition. And then, how do we test whether player actually saw the edit radio interface or not? There is that elusive <span style="font-family:courier new;">EditRadioSettings </span>function being invoked there, maybe we could somehow detect that it's been called? But how?<br />Due to above limitations, unit testing your code seems just not feasible. Surely, there must be some solutions, we just need to go out and learn how the world is doing this.<br /><br /><h3>Mocks</h3><br />The concept is simple. If you need to provide something for the tested code, that's not the part of the test itself but constitutes to the test context, provide a mock. A substitute for real object, a substitute for a function that's gonna allow us to isolate the tested code to the form that may be safely run from within unit test context. As far as above example goes, we would need three mocks:<br /><ul><li>player - Critter object with overriden IsPlayer() method that's returning true (another mock actually), or with fields set in a way that <span style="font-family:courier new;">IsPlayer()</span> is returning true<br /></li></ul><ul><li>an item - Item object with fields telling us it's a radio<br /></li></ul><ul><li>a function to detect that <span style="font-family:courier new;">EditRadioSettings </span>has been called</li></ul>But we can't do this in AngelScript. Not without any modifications.<br /><br /><h3>Mock library</h3>At first I wanted to write some library that would be able to load the script, compile it and execute test functions providing overriden implementations for functions we wanted to mock. However, the AngelScript engine does not allow to re-register any function, so that once we've got our engine set up, we can't register any mocks. We thought of some workarounds, but after some time I decided to modify the server source directly (its angelscript source, to be precise), I suspected it would be very minor modification - so that maintain costs are minimal in the future (keeping it up to date with every server update).<br /><br /><h3>Tests Runner server</h3>After some fiddling in AngelScript engine code, I found out that I can easily 'redirect' function call to another script function (whether the original call was meant to invoke script function, or native (engine registered) one. Moreover, it turned out that method calls can be simply redirected to function calls (providing the first argument is the object passed), without any extra work! This way call to <span style="font-family:courier new;">bool Critter::IsDead()</span> could be handled by <span style="font-family:courier new;">bool critter_IsDead(Critter& cr)</span>. Nice!<br />How does it affect our testing capabilities? To put it simply, we ended up with solution that may be used for testing generally, not only for unit tests. Unit tests are designed to test your small pieces of code out there, but hence we are running our tests in full fledged server, we may as well test the system more broadly, we may check how different pieces interact together - whether it works as a whole or not (though I admit, it wasn't my initial goal - I just wanted simple unit testing facility, not <a href="http://en.wikipedia.org/wiki/Integration_testing">integration tests</a>).<br /><br />But let's get back to the example. Let's start from the last mock we needed, namely, a function substitute for <span style="font-family:courier new;">EditRadioSettings</span>. Say, we just want to know, whether the function has been called or not. Let's mock it, and make it so our substitute will indicate that's been called:<br /><pre class="brush: as">void mock_EditRadioSettings()<br />{<br /> CallExpectation("EditRadioSettings");<br />}<br /></pre><br />For above example to work, we need to know what <span style="font-family:courier new;">CallExpectation </span>function is doing. It simply increases the call counter, that's stored in some dictionary under the index that's been passed as argument. Later on, we may check that the counter is equal to 1 - that means our function has been called as expected. With such dictionary at hand, we should also define more helpers: <span style="font-family:courier new;">Expect(funcname)</span> and <span style="font-family:courier new;">VerifyExpectations()</span>. First one is used to remember the fact, that we want the function <span style="font-family:courier new;">funcname </span>to be called, and the latter (called at the end of the test), will check it. In fact, we should have the ability to specify the numbers of the call we're expecting:<br /><ul><li><span style="font-family:courier new;">ExpectOnce(funcname)</span> - <span style="font-family:courier new;">VerifyExpectations </span>will succeed only if function has been called once<br /></li><li><span style="font-family:courier new;">Expect(funcname, count)</span> - success only if called <span style="font-family:courier new;">count</span> times<br /></li><li>ExpectNonce(funcname) - success if hasn't been called at all</li></ul>Once we've got our mock and our helpers ready, we're gonna substitute our real function:<br /><pre class="brush: as">Mock("EditRadioSettings", "mock_EditRadioSettings")<br /></pre><br />This function will remember that we want to redirect the call to <span style="font-family:courier new;">EditRadioSettings</span> to a function <span style="font-family:courier new;">mock_EditRadioSettings</span>.<br /><br />Ok, what about other mocks? We said that we want an item, and a critter with specific properties and/or we may provide mocks for their methods as well. For this I've implemented simple <span style="font-family:courier new;">MockCritter </span>and <span style="font-family:courier new;">MockItem</span> functions that spawn the needed objects with only basic properties filled, but for the case of this example, let's assume those are just clean objects - with all properties zeroed out, rest will be handled by method mocks:<br /><pre class="brush: as">bool mock_True()<br />{<br /> return true;<br />}<br /></pre><br /><br />And now, our test function (with needed mocks) in full glory:<br /><pre class="brush: as">bool mock_True()<br />{<br /> return true;<br />}<br />void mock_EditRadioSettings()<br />{<br /> CallExpectation("EditRadioSettings");<br />}<br /><br />void IfPlayerUsesRadioEditRadioSettingsShouldBeCalled()<br />{<br /> // prepare<br /> Critter@ player = MockCritter();<br /> Mock("Critter::IsPlayer", "mock_True");<br /> Item@ radio = MockItem();<br /> radio.Flags = ITEM_RADIO;<br /> Mock("EditRadioSettings", "mock_EditRadioSettings");<br /><br /> // call tested function<br /> critter_use_item(cr, radio, null, null, null, 0);<br /><br /> // verify<br /> VerifyExpectations();<br />}<br /></pre><br />Voila! Now, when we call <span style="font-family:courier new;">critter_use_item</span>, it will first call <span style="font-family:courier new;">Critter::IsPlayer()</span>, which returns true, even if our Critter structure might have not indicated this, but our mock did. Later, it will check the flags (we've set it on our mocked item) and call the <span style="font-family:courier new;">EditRadioSettings</span>, which in fact calls <span style="font-family:courier new;">mock_EditRadioSettings</span>. This sets our expectations counter to 1, which is then verified by <span style="font-family:courier new;">VerifyExpectations</span>. And then it announces success - we've got our first trivial unit test passed!<br /><br />The solution we've got is in very early stage, I'm gonna try and involve it in some of the 2238 code testing. We will see if it turns out to be useful.<br /><br />Enough of this mockery for now!scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-16393124817198190882011-08-28T19:10:00.003+02:002011-08-28T19:11:32.729+02:00Server issuesWe're having some problems with the server. We hope to solve it today, but there's no guarantee.
<br />
<br />Thanks for your patience.Ghosthackhttp://www.blogger.com/profile/16339437872106906594noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-65179833115818654732011-08-25T15:00:00.004+02:002011-08-25T15:29:54.577+02:00Faction data - asynchronyThis is gonna be another post from the series on new approach to the faction data storage, and most technical one. It's gonna describe process of creating a native extension for FOnline server application, that will allow communication with external database, in our case, CouchDB. It's already implemented and is being tested, so I guess I can safely write about it.
<br />The endeavour was quite an interesting one with lots of crazy ideas, few dead-ends, but eventually the solution turned out to be nice and clean.
<br />
<br />So, for those interested in hacking the guts of server application, extending it to perform things you haven't imagine, let's read!
<br />
<br /><h3>Data synchronization</h3>First, we need to make brief summary, of what we want to accomplish. It's been described in previous posts, basically, we need a way to synchronize some game data (players properties, variables) with the data stored in CouchDB. As with every communication, there are two directions we may want to communicate:
<br /><ul><li>server asks CouchDB for data it needs to synchronize</li><li>CouchDB tells server that it may want to synchronize</li></ul>Since we'd rather extend server application, than to write additional tier for CouchDB that would communicate with server, we of course choose the first approach. The data we need to synchronize are:
<br /><ul><li>factions
<br /></li><li>players' documents from factions databases</li></ul>So basically we may need two functions to do this:
<br /><ul><li><span style="font-family:courier new;">GetFactions </span>- gets all factions with their properties
<br /></li><li><span style="font-family:courier new;">GetPlayerFile </span>- gets player file stored in certain faction database</li></ul>Let's focus now on the first one.
<br />
<br /><h3>Main factions database</h3><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-Z8pFuDiMMbM/TlZNoJtF79I/AAAAAAAAAEs/XQrsVBV_2Kc/s1600/Alien_Archivist.png"><img style="float:left; margin:0 0 0px 0px;cursor:pointer; cursor:hand;width: 240px; height: 240px;" src="http://2.bp.blogspot.com/-Z8pFuDiMMbM/TlZNoJtF79I/AAAAAAAAAEs/XQrsVBV_2Kc/s400/Alien_Archivist.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5644784535150653394" /></a> Info on our fac tions is going to reside in central database, called <span style="font-style: italic;">fac</span><span style="font-style: italic;">tions</span>. This database is going to contain documents for each of the registered faction (for simplicity sake, we're not going to write about registering new faction process). Example document:
<br /><pre class="brush: js">{
<br /> "_id": "Brotherhood Of Steel",
<br /> "id": 2,
<br /> "database": "brotherhood_of_steel"
<br />}
<br /></pre>
<br />The<span style="font-family:courier new;"> _id</span> field is the unique identifier for the document, so it's also the faction name. The <span style="font-family:courier new;">id</span> (without leading underscore) is the number assigned during registration process, and is the number any member of that faction carries on his player character, so that game logic may react appropriately. The <span style="font-family:courier new;">database </span>field is the name of the faction database, that reside on the same CouchDB server. It needs to be different than the faction name, cause CouchDB does not allow certain characters in database names.
<br />
<br />In-game, our factions are going to be represented by following class:
<br /><pre class="brush: as">class Faction
<br />{
<br /> int id;
<br /> string name;
<br /> string database;
<br />
<br /> Faction(int id, const string& name, const string& database_name)
<br /> {
<br /> this.id = id;
<br /> this.name = name;
<br /> this.database = database_name;
<br /> }
<br />
<br /> int get_Id() const { return this.id; }
<br /> string get_Name() const { return this.name; }
<br /> string get_Database() const { return this.database; }
<br />};
<br /></pre>
<br />And we're storing the objects of that class in some array. So, the only thing we need, is indeed a <span style="font-family:courier new;">GetFactions</span> function, that would just fetch data from <span style="font-style: italic;">factions</span> database and fill up our array.
<br />
<br />But before we dive into code, let's pause for a moment. It is external storage, it's http protocol. By no means it's going to be fast. We can't just write an extension function that is going to perform http request and return the results so that we can process them further in scripts. It would block the server during the call for at least few miliseconds, but it could even be few seconds, it depends. We need a way for asynchronous calls.
<br />
<br /><h3>Asynchronicity</h3>What's that, and why it's not The Police album? The principles of asynchronous calls are very simple. The call is being performed, and the function immediately returns to the place from where it's been called, allowing main thread to resume its job without waiting for function results (while the function that's been called asynchronously is being executed in other thread). Great, but we <span style="font-weight: bold;">are</span> interested in those results, so we need a way to operate on them. Traditionally, this issue is being solved by <span style="font-style: italic;">callbacks</span>. To put it simply, you are defining another function, that will be called when our function that has been called asynchronously finishes its execution. Such callback takes what's been returned by the asynchronous function as its argument, and perform whatever logic we wanted to be performed on that result in the first place.
<br />
<br />But we're not at home still, let's check it. This is going to be our hypothetical extension function, that is going to perform http requets to CouchDB, and return data for further processing (pseudocode, native):
<br /><pre class="brush: cpp">void AsyncGetFactions(callback)
<br />{
<br /> QueueThreadPoolTask(task, callback);
<br />}
<br />void task(callback)
<br />{
<br /> string res = CouchDB::Get("factions");
<br /> callback(res);
<br />}
<br /></pre>
<br />This is how asynchronous functions look like. They only queue the task to be performed later on <span style="font-weight: bold;">other </span>thread. The function is taking a callback as an argument, performs http <span style="font-family:courier new;">GET </span>request, and calls the callback to operate on data. But we want our callback to be AngelScript function, what we may do about it? Common solution in FOnline scripting, is to pass the name of the function as string(pseudocode, AngelScript):
<br /><pre class="brush: as">AsyncGetFactions("callback");
<br />
<br />void callback(const string& result)
<br />{
<br /> // operate on what's been returned. It's JSON, but we're not going to dive into that matter now
<br />}
<br /></pre>
<br />Familiar? Should be - <span style="font-family:courier new;">CreateTimeEvent </span>works this way. We may rework our native extension to something like this:
<br /><pre class="brush: cpp"></pre><pre class="brush: cpp">void AsyncGetFactions(const string& callback)
<br />void AsyncGetFactions(callback)
<br />{
<br /> QueueTask(task, callback);
<br />}
<br />void task(callback)
<br />{
<br /> string res = CouchDB::Get("factions");
<br /> CallAngelScriptFunction(callback, res);
<br />}
<br /></pre>
<br />The last line uses a function, that would fetch the function from AngelScript engine, knowing its name, and would call it passing our result as argument.
<br />
<br />But hey, something is still wrong here. Remember the <span style="font-family:courier new;">task </span>is being executed in another thread. Whoops, and a big one. That means our callback function will be executed on that other thread as well, which means simultaneously with whatever other game logic is being executed at that time. This of course may lead to the data corruption, hard to track errors, unexpected behaviour, all that fun. What can we do about it? Traditionally, you may assure that the function that's going to be run in parallel with main code, does not operate on the same variables that the main logic thread does. But that would be very hard to achieve, after all, for our callback we would probably like to reuse whatever code we alredy have in our codebase, and I bet most of it is not thread-safe at all. Is there something that can save our asses in our quest to achieve our goal?
<br />
<br /><h3>Message passing concurrency</h3>Some clever folks found out, that to avoid problems with multithreaded code, it's best to avoid the code that's simultaneously executed and operates on same set of data - brilliant, isn't it. Instead, it's better to have totally separated units, and only have them communicating between by passing messages to each other.
<br />Notice, that we may safely run our game logic in parallel with CouchDB http request, as those are totally separated. It's when we want to operate on data returned, when we're running into problems. So why not return the data somehow to the server application, and let server logic be the one responsible for reading it and performing the funcionality? That way, the callback logic will be executed by main thread, so no problems here. For that, we need a queue with messages, that we will be using to exchange data between our asynchronous functions, and the server logic. Then we're gonna be able to leave the message (from the other thread), and fetch the message (from main logic thread) to further operate on it (still in main logic thread). That way, the only structure shared between threads will be the queue itself, but it's not a problem to write such thing to be perfectly thread safe:
<br /><pre class="brush: cpp">void PushMessage(msg)
<br />{
<br /> lock(messages);
<br /> messages.push(msg);
<br /> unlock(messages);
<br />}
<br />msg FetchMessage()
<br />{
<br /> lock(messages);
<br /> msg = messages.pop();
<br /> unlock(messages);
<br /> return msg;
<br />}
<br /></pre>
<br />Above pseudocode shows us, how we can synchronize the reads and writes on our queue, to assure that only one thread is accessing them at a time. The way we do it depends on libraries we're gonna use, I do not want to dive into the details here, but the principles are the same:
<br /><ul><li>lock puts a lock on some structure</li><li>unlock takes that lock away from it</li><li>there may be only one lock at the structure at a given time, so next call to lock (performed from other thread) is going to be blocked and wait till it's being unlocked</li></ul>That gives us thread-safety for the queue. Let's use it now:
<br />
<br /><pre class="brush: cpp">void AsyncGetFactions()
<br />{
<br /> QueueThreadPoolTask(task, callback);
<br />}
<br />void task()
<br />{
<br />string res = CouchDB::Get("factions");
<br />// iterate over res content, to send message for every faction contained there
<br />for(...)
<br /> PushMessage(new Message(MESSAGE_FACTION, res));
<br />}
<br /></pre>
<br />
<br />And in script:
<br /><pre class="brush: as">void UpdateFactions() // we could call it from main@loop() for example
<br />{
<br /> AsyncGetFactions();
<br />
<br /> while(true)
<br /> {
<br /> Message@ msg = FetchMessage();
<br /> if(!valid(msg)) break;
<br />
<br /> if(msg.type == MESSAGE_FACTION)
<br /> ProcessFaction(msg.res);
<br /> }
<br />}
<br />void ProcessFaction(string res)
<br />{
<br /> // parse our input, determine faction properties, check if already in array
<br /> // if not, add it there, otherwise - update
<br />}
<br /></pre>Notice, that we're calling <span style="font-family:courier new;">AsyncGetFactions </span>in each loop and after that we're fetching all messages. But the messages won't probably arrive at that moment, for that we will have to wait. And, while we are waiting, there is no point in calling <span style="font-family:courier new;">AsyncGetFactions </span>over and over again. We need to orchestrate somehow our calls, we can do this with simple boolean switches:
<br />
<br /><pre class="brush: as">bool GettingFactions = false;
<br />
<br />void UpdateFactions()
<br />{
<br />if(!GettingFactions)
<br /> AsyncGetFactions();
<br />
<br />while(true)
<br />{
<br /> Message@ msg = FetchMessage();
<br /> if(!valid(msg)) break;
<br />
<br /> if(msg.type == MESSAGE_FACTION)
<br /> ProcessFaction(msg.res);
<br />}
<br />}
<br /></pre>
<br />It's that simple. But we need a way to notice the game logic about the fact, that our asynchronous extension has finished with getting factions. For this, we will extend it to send yet another message, after all faction messages have been sent:
<br /><pre class="brush: cpp">void task()
<br />{
<br /> string res = CouchDB::Get("factions");
<br /> // iterate over res content, to send message for every faction contained there
<br /> for(...)
<br /> PushMessage(new Message(MESSAGE_FACTION, res));
<br /> PushMessage(new Message(MESSAGE_GET_FACTIONS_DONE));
<br />}
<br /></pre>
<br />
<br />And then in scripts, we will switch our variable:
<br /><pre class="brush: as">void UpdateFactions()
<br />{
<br />if(!GettingFactions)
<br />AsyncGetFactions();
<br />
<br />while(true)
<br />{
<br /> Message@ msg = FetchMessage();
<br /> if(!valid(msg)) break;
<br />
<br /> if(msg.type == MESSAGE_FACTION)
<br /> ProcessFaction(msg.res);
<br /> if(msg.type == MESSAGE_GET_FACTIONS_DONE)
<br /> GettingFactions = false;
<br />}
<br />}
<br /></pre>
<br />By setting <span style="font-family:courier new;">GettingFactions </span>to false, we're indicating that we are no longer running <span style="font-family:courier new;">AsyncGetFactions </span>in the background, so we can safely call it again next time. And our loop is chewing whatever messages arrive there all the time. All in parallel, all in thread-safety.
<br />
<br />I hope it was interesting read, maybe not too detailed and with lots of pseudocode, but I did want to show the idea, not the implementation specifics.
<br />scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-73389053944432541892011-08-21T18:18:00.006+02:002011-08-21T19:40:09.228+02:00Tremendous bug!It didn't take you long to find out what was wrong in the solution I presented in <a href="http://fonline2238.blogspot.com/2011/08/what-would-be-point-of-having.html">http://fonline2238.blogspot.com/2011/08/what-would-be-point-of-having.html</a>. In fact, you were faster than me fixing it, after the bug introduced by it occured on 2238 server.
<br />
<br />Let's start from beginning. In first iteration of gathering (called <span style="font-style: italic;">production</span>) back then, we needed a notion of <span style="font-style: italic;">facility </span>that player could use to obtain some resource. A facility could be one item (item pretending to be scenery object actually, it had to be item because of the scripting possibilities), or collection of items. So, a barrel in Modoc was one facility and all the plants somewhere on the crop could be one facility as well. And when the player obtained some resources from it, we needed to apply timeout for it (store it on the item somewhere). Also, we needed even more scripting possibilities - rotgut still for example. Perfect situation to use the wrapping pattern:
<br />
<br /><pre class="brush: as">interface IFacility
<br />{
<br />bool CheckTimeout();
<br />void Use(Critter& cr);
<br />}
<br />class BarrelFacility : IFacility
<br />{
<br />Item@ barrel;
<br />BarrelFacility(Item& item)
<br />{
<br /> this.item = item;
<br />}
<br />bool CheckTimeout()
<br />{
<br /> return item.Val1 > __FullSecond;
<br />}
<br />void Use(Critter& cr)
<br />{
<br /> if(CheckTimeout())
<br /> {
<br /> // give junk
<br /> item.Val1 = __FullSecond + 60; // 60 seconds of barrel cooldown
<br /> }
<br />}
<br />}
<br /></pre>
<br />
<br />Of course above code is simplification, and we needed to wrap the item for other reasons as well - for example having one facility object per many items, but it's not the point of that post to explain all of it.
<br />So, we were adding the <span style="font-family:courier new;">BarrelFacility </span>objects in barrel script:
<br /><pre class="brush: as">void item_init(Item& item, bool)
<br />{
<br />AddFacility(BarrelFacility(item));
<br />}
<br /></pre>
<br />And we had that global array of <span style="font-family:courier new;">IFacility </span>objects. I didn't implement removal procedure, simply because, all those facilities were in towns and other fixed locations. So, they were destroyed only when server was being shut down, and were created only on server start. Additionaly, because of the fact there was only few of them, and I think I haven't got any unused <span style="font-family:courier new;">Item::ValX</span> field, I haven't been storing the index to the array for fast fetch (like I wrote in previous blogpost on that), but I was iterating over whole array to find proper object. It hadn't got big performance impact, but one day we decided we need to change the system: we introduced facilities into encounters. <span style="font-weight: bold;">Boom!</span>
<br />
<br />Yes, that was it - the <span style="font-family:courier new;">IFacility </span>objects were created very often, and never being destroyed. The array was growing and growing, and the fetching procedure was <span style="font-family:courier new;">O(n)</span> in complexity instead of <span style="font-family:courier new;">O(1)</span>. Terrible stuff. When players started to jumping into encounters, after some time the array was so big that every time someone wanted to use facility, the algorithm had to go through thousands of iterations to find proper index in the array. And the appending was quite painful as well - the underlying <span style="font-family: courier new;">array </span>object needs to copy all elements on resize, as it does not reserve space up-front.
<br />
<br />And that caused a lag. The Lag in fact, it was so big that it had destroyed the fun from early beta days. And we haven't fixed it that fast. To put it simply - I forgot that the objects are never removed, and found that out later... A little embarassing, I know, but now, when looking at it after such amount of time - it's quite funny to recall.
<br />
<br />And quite funny story about 2238 development I think.
<br />scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-38250124704231979982011-08-17T23:55:00.015+02:002011-08-22T12:55:30.631+02:00Wrapping a wrap<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-vVjoIXW6dj8/Tk_9C7DD99I/AAAAAAAAAEk/hpz9ApdOXOo/s1600/cevlenin2.png"><img style="float: right; margin: 0pt 0pt 10px 10px; cursor: pointer; width: 230px; height: 290px;" src="http://4.bp.blogspot.com/-vVjoIXW6dj8/Tk_9C7DD99I/AAAAAAAAAEk/hpz9ApdOXOo/s400/cevlenin2.png" alt="" id="BLOGGER_PHOTO_ID_5643007084770621394" border="0" /></a>What would be the point of having developer's blog, if there wouldn't be anything on programming. Oh yeah, that's where the things got hot. This is where it works, this is where it fails.
<br />
<br />Since launch of the SDK, probably lots of people started struggling with that fancy scripting language - AngelScript. I remember I did. Experienced some successes, failures, helped finding some bugs, worked my way out through this language, started appreciating it, started even liking it (now I see it was indeed quite a good choice). That's why I think we could from time to time talk about scripting in FOnline SDK, talk about the AngelScript, share some common pitfalls, solutions that worked out good, and even the ones that weren't.
<br />
<br />Of course basics of angelscripting is required, there won't be any tutorials whatsoever.
<br />
<br /><h3>Objects</h3>Blah blah blah, everything you can model in your OO language is an object, yeah, we already know that and it sucks (I'm getting a little bit off topic right here). Ok, it does not suck, in fact we're gonna often rely heavily on that fact, and we're gonna put large part of our logic into objects. And we're gonna tie those objects to the in-game objects (critters, items, scenery, maps, locations).
<br />
<br />Say we've got a class for objects that are gonna make us some nice abstraction layer over default <span style="font-family:courier new;">item </span>functionality:
<br />
<br /><pre class="brush: as">class SuperFoo
<br />{
<br /> Item@ item;
<br /> SuperFoo(Item& item)
<br /> {
<br /> @this.item = item;
<br /> }
<br />
<br /> void Bar()
<br /> {
<br /> item.Val0++;
<br /> }
<br />}
<br /></pre>
<br />What's going on here? We wrap an item type in some class, and we're adding some useless code to operate on it? Why the hell would we want it? Well, think about the <span style="font-style: italic; font-weight: bold;">interface </span>you've just gained and you can use throughout your scripts. You may now call: <span style="font-family:courier new;">something.Bar()</span> instead of <span style="font-family:courier new;">item.Val0++</span>, which shows <span style="font-weight: bold;">what </span>you want to do, and not <span style="font-weight: bold;">how </span>are you going to do. That's the difference if you want to write good code. Less imperative, less verbosity, less bugs (yes, I know that example suck, and you would want that for far more reasons - think about having some additional data, that's not stored on the wrapped item, but it's one of the class' fields - it's not saved anywhere, but still may be useful).
<br />
<br />Wait, did someone say <span style="font-style: italic;">interface</span>?
<br /><pre class="brush: as">interface ISuperFoo
<br />{
<br /> void Bar();
<br />}
<br /></pre>
<br />Oh great, now we've got the interface that allows us to call <span style="font-family:courier new;">Bar()</span>. And we don't even know what's called in the implementation class (in fact, we don't care, this is what interfaces are for).
<br />
<br />So far so good, but FOnline scripting engine works in a way, that you can attach your functionality to the handlers for default events.
<br />
<br />Let's start of course, from creating an object. If we want to attach it to the item, we need to do this in the <span style="font-family:courier new;">item_init</span> handler.
<br /><pre class="brush: as">void item_init(Item& item, bool firstTime)
<br />{
<br /> SuperFoo@ foo = SuperFoo(item);
<br />}
<br /></pre>
<br />Yeah, whoops. We're gonna loose the reference to the newly created <span style="font-family:courier new;">foo</span> object as soon as we leave the function. Unacceptable, we need to store it somewhere.
<br />
<br /><h3>Object Managers</h3>This is also common solution. If you've got many objects in your program, you need to manage them somehow, store the references to them, maintain their lifetime, give the references(via interaface) to those interested.
<br />We're gonna use basic generic <span style="font-family:courier new;">array </span>type for that.
<br /><pre class="brush: as">
<br />array<superfoo@> Foos;
<br />
<br />ISuperFoo@ GetFoo(const Item& item)
<br />{
<br /> for(uint i = 0; i < Foos.length(); i++)
<br /> {
<br /> if(Foos[i].item.Id == item.Id)
<br /> return Foos[i];
<br /> }
<br /> return null;
<br />}
<br />void AddFoo(Item item)
<br />{
<br /> Foos.insertLast(SuperFoo(item));
<br />}
<br /></pre>
<br />Oh what a horrible piece of code we've just made. Yes, we've got our array, and function to add an object for an item (which we can use in <span style="font-family:courier new;">item_init</span>) and get the interface of such object for existing item. But there is that horrible loop in GetFoo, we need to scrap that. The best would be, if we could perform fetch with direct index to our array. We can do that, if we're able to store the index, at which our <span style="font-family:courier new;">Foo</span> is located in the array. Let's store it in <span style="font-family:courier new;">Item::Val1</span> field:
<br /><pre class="brush: as">void AddFoo(Item& item)
<br />{
<br /> Foos.insertLast(SuperFoo(item));
<br /> item.Val1 = Foos.length() - 1; // here we've got index to our array stored
<br />}
<br />ISuperFoo@ GetFoo(const Item& item)
<br />{
<br /> if(item.Val1 < Foos.length())
<br /> return Foos[item.Val1];
<br /> else
<br /> return null;
<br />}
<br /></pre>
<br />and our item_init will now look like:
<br /><pre class="brush: as">void item_init(Item& item, bool)
<br />{
<br /> AddFoo(item);
<br />}
<br /></pre>
<br />So, we're now adding our SuperFoo object every time we call item_init, and we can fetch it in any script module (any module that imports this function of course), by obtaining an interface, that's not coupled in any way to the implementation class, nor coupled to the item object. Nifty.
<br />
<br />We can use our interface in following manner:
<br /><pre class="brush: as">void item_init(Item& item, bool)
<br />{
<br /> AddFoo(item);
<br /> item.SetEvent(ITEM_EVENT_USE, "_FooUse");
<br />}
<br />bool _FooUse(Item& item, Critter& crit, Critter@ onCritter, Item@ onItem, Scenery@ onScenery)
<br />{
<br /> ISuperFoo@ foo = GetFoo(item);
<br /> if(valid(foo)) foo.Bar();
<br />}
<br /></pre>
<br />The _FooUse function is where this solution shines. We call our method via interface(we may even pass some parameters given to us by the engine), and we do not care of what's going on inside.
<br />
<br />Now, there is still something wrong with that solution, first who find out what, gets a candy!scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.comtag:blogger.com,1999:blog-6958310958031547406.post-19081837057297401412011-08-16T11:57:00.008+02:002011-08-16T12:35:10.042+02:00Gathering? Waiting?<span style="font-family:verdana;">Oh, that's certainly hot topic. Countless debates, unlimited flames, and cause of infinite hate, frustration, suicides, recession.</span>
<br />
<br />
<br /><span style="font-family:verdana;">Changes are iminent, be it cooldown time reduction or even changing approach to whole system. We'd like to present you couple of ideas that you might want to discuss - yeah like it hasn't been done before countless of times! Jokes aside, let's check it out.</span>
<br />
<br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR2sRkcrWYY5li3z6IoyVK_2Q_VMYrraz5peJU3fQaXb6mVLuN9KBXh_CR733RdMq_cEzo9sEuJOUwqpk1JF70gDNaXgKJEvLyA5vzp3tB4dzIr-hogSur677tnzDT_ZlNsU8WZB4qyA6t/s1600/1313490496-clip-144kb.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 213px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR2sRkcrWYY5li3z6IoyVK_2Q_VMYrraz5peJU3fQaXb6mVLuN9KBXh_CR733RdMq_cEzo9sEuJOUwqpk1JF70gDNaXgKJEvLyA5vzp3tB4dzIr-hogSur677tnzDT_ZlNsU8WZB4qyA6t/s320/1313490496-clip-144kb.jpg" alt="" id="BLOGGER_PHOTO_ID_5641400172924202562" border="0" /></a>
<br /><h3>Scavenging</h3><span style="font-family:verdana;">During early times of open beta, the gathering for resources worked in slightly different manner. You were looking for various materials on the worldmap, there was a chance for particular resource being generated. After you've acquired it, there was no cooldown. This ended in constant entering/looking around/leaving, which was not only rather boring, but also highly farmable. However, the worldmap and encounters are playing big part in Fallout games, so we could tweak that approach, and use it once again:</span>
<br /><ul><li><span style="font-family:verdana;">attach a chance to various zones to spawn various materials (be it junk, ore or whatever)</span></li><li><span style="font-family:verdana;">disable ability to reroll material spawn when you force-enter from worldmap (that means you need to wander around and wait for next encounter check)</span></li></ul><span style="font-family:verdana;">That leaves us with a system in which we still could calculate the average time in which a player is obtaining particular resource: encounter check is being performed every 5 sec, it's easy to calculate values on that.</span>
<br />
<br /><span style="font-family:verdana;">What are the pros of such approach? </span>
<br /><ul style="font-family: verdana;"><li>no cooldown - or rather, it's being hidden</li><li>corelating materials with encounters, that means, due to the level of difficulty, some zones could spawn high quality resources</li></ul><span style="font-family:verdana;">How about the cons?</span>
<br /><ul style="font-family: verdana;"><li>it's all about encounters once again, may be frustrating to look around with no luck</li></ul>
<br /><h3>Hunting</h3><span style="font-family:verdana;">Well, but we could as well stay with current cooldowns, but we need a way for players to reduce them. Somewhat entertaining way, though that's subjective. For that, we could add items that reduce timeout - say food. You could obtain food by hunting (encounters again), or you could buy it at traders. That's additional task your player needs to do, but that's optional (well, some may find it mandatory, if they want to have the most out of it).</span>
<br />
<br /><span style="font-family:verdana;">Pros:</span>
<br /><ul style="font-family: verdana;"><li>additional activities</li><li>a way to reduce cooldown
<br /></li></ul><span style="font-family:verdana;">Cons:</span>
<br /><ul style="font-family: verdana;"><li>The cooldowns stay (albeit reduced)
<br /></li></ul><span style="font-family:verdana;">That's all for now. I think we don't need to encourage discussion, it's unavoidable!</span>
<br />scypiorhttp://www.blogger.com/profile/01268110504843405526noreply@blogger.com