Ossa Sepia

May 24, 2018

A Tiny Stab at a Little Pricing Problem

Filed under: Eulora — Diana Coman @ 10:57 p.m.

To start off, here's Foxy making disgusting goop in large quantities1:

Why is she making that? Well, for profit, what else. With each click, she's transforming 2852 ECu worth of rock-and-what-not into 4324 ECu worth of Disgusting Goop. That might not sound like much, except for the fact that it is effectively a 50% added value just like that in a few minutes (and while I sleep, yes, how else).

The above concrete numbers give me added ECu but also a way of approaching the ever-thorny euloran issue of pricing stuff. Only yesterday it would seem I failed at pricing some WPL correctly since the market (that had ASKED for WPL specifically) decided that... it was too expensive to bother buying it! So no sale made and a fee to pay instead2 but also a very clear and concrete thing to point to next time when anyone asks for WPL or complains that they don't have it.

Regardless of the market being there apparently completely lost in terms of what is how much and why would WPL cost more than dirt-cheap, lowest quality basic stuff, it's still worth to look again at some fresh data and see what prices make sense at all. Taking the Disgusting Goop above, let's calculate precisely what went in and what came out:

input: bundle of quality 595 + blueprint of quality 120 = 5.95*470 + 1.2*47 = 2852 ECu (quality adjusted base value3 )

output: 8 DG of quality 115 = 8*1.15*470 = 4324 ECu (quality adjusted base value)

Output / input = 4324/2852 = 1.51 (i.e. 151% or 51% gain).

Considering that the DG bundle is made only from harvestables4 it follows that Foxy simply can NOT sell those harvestables (rf, cr and sm) at anything less than 150% because at anything less than that she'd be better off using them as input to craft!  Notice also that the basic resources there (rf+cr) make only 2 thirds of the bundle, with sm the other third. For this little exercise I ignored the distinction between basic and non-basic harvestable but mining experience screams loud and clear that non-basic resources ARE harder to get in same quantities and same qualities. Which is not at all surprising given that they have higher value, doh.

As usual, in practice things are indeed a bit murkier, as all sorts of considerations quickly come stomping in: this 50% increase might be obtainable only at this specific sweet spot or only for this specific combination etc. Nevertheless, it IS some concrete data point in a sea of unknowns, so I'll stick to it for now. I'll add also that Foxy is a very skilled crafter (possibly even the top crafter at the moment) and it is for this reason that she *has this option* to make a significant profit just by crafting. Other players might find that the highest profit they can make out of harvestables really is by selling them at less than 150%, why not?

As Foxy's rather busy earning about 1500 ECu every couple of minutes for now, let's leave her aside for a moment and ask simply: what are *you* earning every couple of minutes and what are *your* options really?


  1. Yes, the red messages are all there one on top of the other because the bot is firing requests at the server without pause for clicking, eating, thinking or any of the other human-vices. 

  2. There is a fee for auctions without sale to discourage spam of useless auctions. 

  3. Also known as qabv and in any case the de facto *minimum* value of something in Eulora since that's what you can always get if you sell the item to the merchant NPC (i.e. to S.MG directly). 

  4. harvestables are obtained directly from euloran soil as a result of "mining" activity aka /explore + build; by contrast, craftables are items crafted as above, possibly from harvestables or other craftables 

April 17, 2018

RFC: Eulora's Communication Protocol (EuComms)

Filed under: Eulora — Diana Coman @ 2:59 p.m.

This is a request for public comment (questions, critique, discussion etc) on the current version of Eulora's protocol for client-server communications that has been in works for a while now. Once this public discussion phase ends, the most significant aspects of this protocol will be effectively set in stone, since the procotol is central to any working client for Eulora. Consequently, your chance at having any significant input on it is *now* and only now. Any questions or feedback on it as well as any suggestions or requests related to it are most welcome, warmly appreciated and thoroughly considered *now*. By contrast, any whining and bitching over it at a later time will likely have at most some entertainment value - my entertainment that is. In fewer words: speak now or suffer later, your choice.

1. EuComms: Overall Goals:
1.1. All communications between clients and server to be encrypted.
1.2. Clients to be able to receive from server any data they lack (including maps, skins, sound or video content etcetera), on demand.
1.3. Clients to be able to choose and adjust both the level of security and their volume of communications with the server, as they will ultimately have to pay for the load that they generate.

2. EuComms relies on:
2.1. RSA with Keccak-based OAEP (via EuCrypt) for initial communication of OTPs (Serpent keys).
2.2. Serpent (via EuCrypt) for general communications.

3. Structure:
All communications between server and client will consist of packets up to 4096 bits in length, encrypted either via eucrypt.RSA or eucrypt.Serpent. The complete list of all such packets is included below.

Packets the client may send to the server:
3.1. Initial connection to server : client encrypts with the server's public key and sends to the server a hello message; constructed as follows : following conversation in #trilema log, this hello message would actually end up multi-packet as it has to include the client's public key (n,e so that's currently 4096+2048 bits) instead of the id at 3.1.5 in the structure below:
3.1.1. Octet 0 - the length of the message in octets (43 or 76);
3.1.2. Octets 1 to 8 - the string "EuLoRa";
3.1.3. Octets 9 to 10 - the major and minor versions of the communications protocol that the client wants to use;
3.1.4. Octets 11 to 42 - the client ID string, which will be equal to the eucrypt.keccak hash of the client binary. All client providers will be required to register all their ID strings with Minigame ; connections from clients with unknown ID strings will be rejected. Players will be notified of and helped to identify changes in their client software.
3.1.5. Octets 43 to 75 - the account ID string. If this field is omitted the server will create a new game account for the client.

3.2. Data (client sent)
3.2.1. Octet 0 - the length of the message in octets (multiple of 16 octets);
3.2.2. Octet 1 to 2 - descriptor of the type of data being sent (see annex A for datatypes);
3.2.3. Octet 3 to 5 - three octets ID of the item being sent;
3.2.4. Octet 6 to n - payload.

Packets the server may send to the client:
4.3. Data (server originated)
4.3.1. Octet 0 to 4 - the length of the message in octets (multiple of 16 octets, in principle limited to 65536 altogether and practically in the kbs anyway);
4.3.2. Octet 5 to 6 - descriptor of the type of data being sent (see annex A for datatypes);
4.3.3. Octet 7 to 10 - four octets ID of the item being sent;
4.3.4. Octet 11 to 16 - six octets counter of the chunk being sent (this limits downloadable items somewhat);
4.3.5. Octet 17 to n - payload.

Annex A -- datatypes in client-server communication

NB: all data types below can in principle be sent by both client and server; however, data sent by the client is essentially a request to the server (i.e. it’s up to the server to decide whether it is valid and/or has any effect and/or what effect in game) while same data sent by the server is feedback/information (i.e. it reflects something that *has happened/exists already*).

In fewer words: client proposes, server disposes.

A.0 Basic types (used throughout the rest of sections in this Annex A)
A.0.0. char - character value on 1 octet
A.0.1. text - n*char where n will ALWAYS be specified clearly, right next to each and every text variable passed around
A.0.2. int8 - integer value on 1 octet
A.0.3. int16 - integer value on 2 octets
A.0.4. int32 - integer value on 4 octets
A.0.5. int64 - integer value on 8 octets
A.0.6. float - real value on 4 octets

A.1 Connection
A.1.1 Set of Serpent Keys

  • int8 - number of keys in this set, n;
  • n*(4*int64 + int32) octets - n Serpent keys (32 octets each + 4 octets ID for each, calculated as keccak hash of the 32 octets);
  • int8 - 1 for server use, 2 for client use, anything else undefined currently.

A.1.2 Request Serpent Keys --> response is a "Set of Serpent Keys" (A.1.1)

  • int8 - number of keys requested;
  • int8 - value 1 for server use, 2 for client use, anything else undefined currently.

A.1.3 Choose Serpent Key

  • int32 - ID of the key that will be used for communications from now on (depending on the key's set, it'll be used for server's or client's messages).

A.1.4 Burn & Choose Serpent Key

  • int32 - ID of the key to remove from usable sets;
  • int32 - ID of the key to use for future comms;

A.1.5 Unknown/Malformed Message received

  • int64 - id of message that was not understood.

A.2 File Transfer
A.2.1 File Information

  • int8 - length of file name in octets, n
  • n*char - file name (string)
  • int8 - file type
  • int64 - octets per unit
  • int64 - file size in unit given above
  • int32 - file hash (keccak)

A.2.2 File Content

  • int32 - file hash (keccak)
  • int64 - octets per unit
  • int64 - file size in unit given above
  • unit*file_size*int8 - file content

A.2.3 Request File Information --> response is a "File Information" (A.2.1)

  • int8 - length of file name in octets, n
  • n*char - file name (string)

A.2.4 Request File Content --> response is a "File Content" (A.2.2)

  • int8 - length of file name in octets, n
  • n*char - file name (string)

A.2.5 Request File Information --> response is a "File Information" (A.2.1)

  • int32 - file hash (keccak)

A.2.6 Request File Content --> response is a "File Content" (A.2.2)

  • int32 - file hash (keccak)

A.3 Chat
A.3.1 Chat Public

  • int64 - id of speaker
  • int16 - length of string being said
  • n*char - string being said

A.3.2 Chat Private

  • int64 - id of target for this chat
  • int64 - id of speaker
  • int16 - length of string being said
  • n*char - string being said

A.4 Character Actions & Activities
A.4.1 Basic action

  • int64 - id of subject
  • int64 - id of object
  • int32 - id of action attempted (client-sent)/performed (server-sent) --> see Annex B for list of current basic actions

A.4.2 Item movement

  • int64 - id of subject
  • int64 - id of object
  • int64 - quantity (stack count) to be moved
  • int64 - id of destination object (i.e. own char's id for "pickup", container ID for move to container, sector id for "drop" or move from container to world, other char's id for "trade" or "store" etc.)

A.4.3 Item movement with specified slot

  • int64 - id of subject
  • int64 - id of object
  • int64 - quantity (stack count) to be moved
  • int64 - id of destination object (i.e. own char's id for "pickup", container ID for move to container, sector id for "drop" or move from container to "world", other char's id for "trade" or "store" etc.)
  • int64 - slot id

A.4.4 Activity --> this is meant to be sent by the server;

  • int64 - id of activity
  • int64 - duration (in seconds)
  • int64 - start time (unix format)
  • int8 - status: 0 -> finished/success; >0 -> ongoing / scheduled; <0 -> failed / aborted

A.4.5 Request Activity Information --> response is "Activity" above (A.4.4)

  • int64 - id of requested activity

A.4.6 Train

  • int64 - id of character
  • int64 - id of skill to train

A.5 Environment & Graphics
NB: "requests" here are not actions of the character in the game as such but rather supporting raw API for the client; for instance, requests here include obtaining the id of own character or available maps.

A.5.1 List of Sectors

  • int64 - number of sectors, n
  • n*int64 - list with ids of available sector

A.5.2 Request list of sectors --> response will be "List of Sectors" above (A.5.1)

  • int64 - character ID

A.5.3 Sector Map

  • int64 - id of sector
  • int8 - length of map filename (sector's name)
  • n*char - map filename (sector's name)

A.5.4 Request sector map --> response will be "Sector Map" type above (A.5.3)

  • int64 - id of sector

A.5.5 Item in sector (actor/character or object)

  • int64 - id of item
  • int64 - id of sector in which the item is located
  • float - position of item: x coordinate
  • float - position of item: y coordinate
  • float - position of item: z coordinate
  • int64 - id of graphics profile --> graphics profiles are so that the client can request and choose from alternative/equivalent actual files/meshes/icons/animations for this particular graphics profile
  • int8 - length of name/label of item
  • n*char - name/label of item

A.5.6 Request own char --> no payload here really since it's accepted only from authenticated client so unambiguous anyway; response will be a "Item in sector" type above - it includes id of sector + id of char etc. (A.5.5)

A.5.7 Item in container

  • int64 - id of item
  • int64 - count
  • int64 - parent item/actor id (i.e. inventory is simply actor-as-container)
  • int64 - slot id
  • int64 - id of graphics profile --> same story as at A.5.5 above
  • int8 - length of name/label of item
  • n*char - name/label of item

A.5.8 Request item --> response will be "Item in sector" or "Item in container" types above (A.5.6, A.5.7)

  • int64 - id of item

A.5.9 Item Text Description (returned in response to a "view" basic action)

  • int64 - id of item
  • int64 - length of description
  • n*char - text description

A.5.10 Item Text Content --> sent in response to "read" basic action

  • int64 - id of item
  • int64 - length of text content
  • n*char - text content

A.5.11 Item List of Characteristics

  • int64 - id of item
  • int16 - number of characteristics
  • n*int64- the ids of relevant characteristics for this item

A.5.12 Item Characteristic

  • int64 - id of item
  • int64 - id of characteristic
  • int64(/float/int32/int16/char) - value of characteristic --> type here would depend on characteristic according to definition at A.5.13 below

A.5.13 Characteristic

  • int64 - id of characteristic
  • int8 - type of value of this characteristic (see A.0 above for the ids of data types that can appear here)
  • int64 - length of name of characteristic, n
  • n*char - name of characteristic
  • int64 - length of name of category, c
  • c*char - name of category

5.14 Request Characteristic --> response is "Characteristic" above (A.5.13)

  • int64 - id of requested characteristic

A.5.15 Skill

  • int64 - id of skill
  • int64 - length of skill name
  • n*char - skill name
  • int64 - length of skill's category name
  • c*char - category name

A.5.16 Skill List

  • int 64 - number of skills in this list
  • n*int64- ids of skills in this list

A.5.17 Character's Skill

  • int64 - id of character
  • int64 - id of skill
  • int64 - current rank
  • int64 - current knowledge points
  • int64 - current practice points
  • int64 - cost of knowledge to next rank
  • int64 - cost of practice to next rank

A.5.18 Request Skill Information --> response will be Skill above (A.5.15)

  • int64 - skill id

A.5.19 Request List of Skills --> response will be Skill List above (A.5.16)

  • int64 - id of character whose skills are requested

A.5.20 Request Character's Skill --> response will be Character's Skill above (A.5.17)

  • int64 - id of character whose current skill values are requested
  • int64 - id of skill requested

A.5.21 Time of day (game time)

  • int8 - hour
  • int8 - minute

A.5.22 Request time of day (game time) --> no payload, response is Time of day above (A.5.21)

A.5.23 Movement Type

  • int64 - id of this movement type
  • int64 - id of graphic profile for this movement type
  • float - x change
  • float - y change
  • float - z change
  • float - x rotation
  • float - y rotation
  • float - z rotation
  • int64 - length of name
  • n*char - name of this movement type

A.5.24 Request movement type --> response will be "Movement Type" above (A.5.23)

  • int64 - id of requested movement type

A.5.25 List of movement types

  • int64 - number of available movement types
  • n*int64- list of ids of available movement types

A.5.26 Request list of movements --> response will be a "List of movement types" above (A.5.25)

  • int64 - id of item for which available movement types are requested

A.5.27 Set Movement Type

  • int64 - id of item for which movement type is set
  • int64 - id of movement type to set

A.5.28 Destroy Item

  • int64 - id of item that is destroyed/vanishes

A.5.29 Set Item Position

  • int64 - id of item
  • int64 - id of sector in which the item is located
  • int8 - position of item: x coordinate
  • int8 - position of item: y coordinate
  • int8 - position of item: z coordinate

A.5.30 Neutral message --> mainly for feedback e.g. "X killed himself", "That tool is not useful anymore", "You ranked up.", "You got loot"

  • int64 - length of text in characters, n
  • n*int8 - text

A.5.31 Error message (in game)

  • int64 - length of text in characters, n
  • n*int8 - text

A.5.32 Success message (in game)

  • int64 - length of text in characters, n
  • n*int8 - text

A.5.33 Message of the day

  • int64 - length of text in characters, n
  • n*int8 - text

A.5.34 Request message of the day --> no payload here; answer will be "Message of the day" above (A.5.33)

A.5.35 Top

  • int64 - duration in days from game's "today" (i.e. 0 for today's top, 30 for past months' top etc.)
  • int64 - length of text in characters, n
  • n*int8 - text

A.5.36 Request Top --> answer will be "Top" above (A.5.35)

  • int64 - duration in days from game's "today" (i.e. 0 for today's top, 30 for past months' top etc.)

A.5.37 Graphics Profile

  • int64 - id of this graphics profile
  • float - width
  • float - height
  • float - depth
  • int64 - number of contained slots with their own graphics profiles
  • int8 - length of filename for icon
  • m*int8 - icon filename
  • int8 - length of filename for mesh
  • n*int8 - mesh filename
  • int8 - length of filename for texture
  • p*int8 - texture filename
  • int8 - length of filename for animation
  • q*int8 - animation filename
  • int8 - length of filename for sound
  • r*int8 - sound filename

A.5.38 Request Top-Level Graphics Profile --> response is "Graphics Profile" above (A.5.37)

  • int64 - id of requested graphics profile

A.5.39 Request Child Graphics Profile --> response is "Graphics Profile" above (A.5.37)

  • int64 - length of path (i.e. how deep in the hierarchy is the graphics profile of interest)
  • n*int64- ids forming the path to the desired graphics profile (e.g. 2,1,15 means the 15th slot of the 1st slot of top graphics profile number 2).

Annex B - List of currently available basic actions

B.1 Target --> this is less than View at B.2. below; character targets the object.

B.2 View --> this is a generic view and can be applied to anything (objects, actors, world included).

B.3 Read

B.4 Lock/unlock

B.5 Use/Combine/Sacrifice

B.6 Explore (current /explore, with target sector id)

B.7 Repair

B.8 Exchange --> this would be used for at least 3 currently distinct parts: trade, store, bank. As everywhere else, its exact effect depends on context.

B.9 Complete --> for actions that require explicit "accept" confirmation from player (e.g. trade)

B.10 Abort --> aborts what the character is doing (if the abort is possible, permitted and meaningful in the given context).

B.11 Attack --> for current "/die" simply use this with subject and object the same, namely own character.

Use the comment form below for any questions, comments, suggestions or desires you have regarding this communications protocol.

May 3, 2017

Quest for the Crafty Noob

Filed under: Eulora — Diana Coman @ 5:19 p.m.

Eulora is this game where players get to actually do... everything. Even give quests to other players, when they want to and as (or if!) they like it. No more "bring me 7 rats' tails and I'll give you a rat's ass" a la every other RPG out there but good old fashioned elder talking1: here son, take ye olde blueprint and craft yourself a table first if you are serious about actually doing something in this life!

The table in question is not any table but the very useful and extremely versatile craft-table that Foxybot uses to carry stuff around when one doesn't use it to craft or to store or to expand one's inventory. And so the quest goes like this:

Find Foxy in game and ask her for the Crafty Noob's first quest. Ask her nicely for she is under no obligation to give the quest to you! If she is however of a nice enough disposition to give you a helping hand, she'll give you a few items, including a craft-table blueprint that reads like this:

craft-table_bp

She'll also give you a bit of advice: take those items and go find the public crafting table that is close to one NPC called Electron Spirover, up a rocky hill. Once there, figure out how to use the things she gave you (as well as other bits that you might need but can find yourself with a bit of /explore) and make yourself your very own Craft-table!

As a bonus, you might even get some numina in the process. Or at least find out that you... could have gotten some numina such as Petrified Feelings. If only you knew how and why or when one gets such things...


  1. Do you even still find this in *real* life around you? Did you ever look for it? 

April 13, 2017

Bundling with Foxybot

Filed under: Eulora — Diana Coman @ 3:04 p.m.

The ever useful Foxybot got shot a bit in one of its many legs at the last euloran update - previously fixed and reliable lists of ingredients turned all flighty and fickle, with ingredient numbers changing even as one crafts. As a result, automated craft runs can turn into a bit of a pain to both plan for (how many ingredients does one even NEED if each click potentially requires a different amount??) and to execute flawlessly and tirelessly over days and weeks, all foxybot-style. Still, people found a way around this trouble: just bundle stuff upfront and then ...craft it, build it, eat it or whatever else you want to do to it, changing blueprint or not. And if bundling is the new planning and the new pre-requisite to successful crafting for a useful bot, let's teach Foxybot to bundle, shall we?

You'll need all of half an hour time and a bit of thinking - hopefully that's something you *can* find around your house if not about your person at any given time. Grab your current version of the Foxybot and open its botactivity.h file. We'll make bundling a new activity for the bot so simply plonk in there the declaration of this new activity, similar to the other ones you'll see in the file. Since code is really the place for precision, not imaginary friends and flourishes, we'll call it simply:

class BundleActivity: public BotActivity, iTimerEvent
{
};

The above lines declared the new BundleActiviy as a refined version of the generic BotActivity and of iTimerEvent. Basically so far it just brings those two together and it does nothing more than that. Clearly that's not enough, so let's add some useful methods and fields between those two curly brackets where the body of our new activity is to be defined:

public:
	BundleActivity();	//constructor
	~BundleActivity();	//destructor
	void HandleMessage(MsgEntry* message );	//this will react when server says something we care about!
	void ScheduleNewPerform(int delay);	//this is like an alarm clock - wake up lazy bones and do the next step
	bool Perform(iTimerEvent *ev);		//this IS the next step, called each time the alarm clock rings

	//for iBase			//those are the price of using crystal space; don't get me started.
	void IncRef(){}
	void DecRef(){}
	int GetRefCount() {return 0;}
	void* QueryInterface(scfInterfaceID i, int a) {}
	void AddRefOwner(void **, CS::Threading::Mutex*) {}
	void RemoveRefOwner(void**){}
	scfInterfaceMetadataList* GetInterfaceMetadata() {return NULL;}

protected:
	void StartWork();	//this is starting point, gotta start from somewhere, no?
	void WrapUp();		//at the end of each cycle one has to tidy up and this is just the code to do it
	void DoInit();		//at the start of each cycle one has to get all tools out and get everything ready

private:
  recipe rcp;		//this is the blueprint saying WHAT and HOW we are to bundle; can't do without it
  bool bundleReady;	//logic here is so simple that we can get away with just a few flags: is bundle ready?
  bool recipeReady;	//is recipe ready/read?
  bool bundlingStarted;	//has bundling even started?
  int containerID;	//id of container used for mixing; needed to filter view container messages for the correct one
  EID containerEID;	//this is Planeshit idea of ids: making at least 2 for each thing and several more to spare.
  int percent;	//used for choosing number of ingredients	- if only it would be percent...

Save botactivity.h and close it. Open botactivity.cpp and let's now actually write all those things we just promised in the .h file. Ain't no code working on empty promises and fancy headers, you know? So there we go, plonk in botactivity.cpp:

BundleActivity::BundleActivity()
{
  name = csString("bundle");		//we need this to react to /bot bundle
  textcolour = TEXT_COLOUR;		//what colour foxybot speaks in for this activity.
}

BundleActivity::~BundleActivity()
{//nothing to destruct really!
}

void BundleActivity::HandleMessage(MsgEntry* message )
{
	if (!IsOngoing())	//if I'm not working, then I don't care what server says!
	  return;
	//only if we are actually looking for bundle
	if (!bundleReady && message->GetType() == MSGTYPE_VIEW_CONTAINER)
	{//did things change in that container?
		psViewContainerDescription mesg(message, psengine->GetNetManager()->GetConnection()->GetAccessPointers());
		if (mesg.containerID == containerID)	//this is for the container we are working with, check if there is a bundle
		{//kind of lulzy if we were to look in the WRONG container, so check id, yeah

			if (mesg.contents.GetSize()>0 && mesg.contents[0].name.Find("Bundle") < mesg.contents[0].name.Length()) //it is a bundle { bundleReady = true; //hey, we've got a bundle, we can go ahead! } } } else if (!recipeReady && message->GetType() == MSGTYPE_CRAFT_INFO)
	{	//this is when we need to read the recipe/blueprint that is equipped
		psMsgCraftingInfo incoming(message);
		csString itemDescription(incoming.craftInfo);

		rcp.ParseRecipe(itemDescription, percent>50);
		worldHandler::CloseOpenWindow(csString("Read Book"));
		OutputMsg(csString("Done reading recipe."));
		recipeReady = true;
	}
}

void BundleActivity::ScheduleNewPerform(int delay)
{
	csRef<iEventTimer> timer = worldHandler::GetStandardTimer();
	timer->AddTimerEvent(this, delay);	//set alarm clock
}

bool BundleActivity::Perform(iTimerEvent *ev)
{
	if (!IsOngoing() && !IsFinished())	//if I ain't working, I ain't performing.
		return false;

	if (timesDone >= timesToRepeat)		//done is done
	{	//nothing more to do here.
		Finish();
		return false;	//this means DON'T set up that alarm anymore!
	}

	if (!recipeReady)	//reading comprehension trouble? Just ask to read again. And again and ...
	{
		//ask for it again and wait another round
		psViewItemDescription out(CONTAINER_INVENTORY_EQUIPMENT, PSCHARACTER_SLOT_MIND);
		out.SendMessage();
		return true;	//re-schedule
	}
	else 	//recipeReady - we READ it, hooray
		if (!bundlingStarted)	//if it's not started, then...we start it now; simple!
		{

			OutputMsg(csString("Bundling action combining..."));
			char out[200];
			sprintf(out, "Done %d items, %d left to do.", timesDone, timesToRepeat-timesDone);
			OutputMsg(csString(out));

			if (!worldHandler::TargetEID(containerEID))
			{
				OutputMsg(csString("Could not find container within reach!"));
				Error();
				return false;
			}

			if (!worldHandler::OpenTarget())
			{
				OutputMsg(csString("Could not open the container!"));
				Error();
				return false;
			}
			OutputMsg(csString("Moving ingredients to container for bundling"));
			//move ingredients from inventory to container
			//first check for any bundles

			int toContainer = containerEID.Unbox();

			csHash<int, csString>::GlobalIterator iterIngredients(rcp.GetIngredientsList()->GetIterator());

			int nextEmptySlot = 0;
			if (!iterIngredients.HasNext())
			{
				OutputMsg(csString("Empty ingredients list!"));
				Error();
				return false;
			}
			while (iterIngredients.HasNext())
			{
				csString itemName;
				int quantity = iterIngredients.Next(itemName);

				char out[1000];
				sprintf(out, "Ingredient %s: %d", itemName.GetData(), quantity);
				OutputMsg(csString(out));

				psInventoryCache::CachedItemDescription* from = worldHandler::FindItemSlot(itemName, false);
				if (!from || from->stackCount < quantity) { OutputMsg(csString("Not enough ingredients for bundling! Bot stopping.")); Error(); return false; } else { worldHandler::MoveItems(from->containerID, from->slot, toContainer, nextEmptySlot, quantity);

				}
				nextEmptySlot = nextEmptySlot + 1;
			}
			OutputMsg(csString("Done with ingredients. Starting to combine."));

			worldHandler::CombineContentsInTarget();

			bundlingStarted = true;
			return true;	//re-schedule
		}
		else if (!bundleReady)	//we know bundling has started but..no bundle yet
		{
			if (csGetTicks() - startTime >= timeout)	//we have SOME patience; but no more than timeout
			{
				OutputMsg("Timedout, moving on.");
				//take items, ask for unstick + start over
				worldHandler::TakeAllFromTarget();
				worldHandler::ExecCmd(csString("/unstick"));	//this is so that ongoing actions are cancelled as otherwise everything is jammed afterwards
				DoInit();	//start all over again;
			}
			else
			{//maybe it IS done, but we...missed the memo; ask for it again
				//ask for container contents, maybe this got lost somehow
				worldHandler::OpenTargetEID(containerEID);
			}
			return true;	//simply reschedule here anyway, either way
		}
		else //all of them are true, so we're done, grab bundle + start again
		{
			//take items
			worldHandler::TakeAllFromTarget();
			Finish();
			if (timesDone < timesToRepeat) //if we are not done, go ahead
                        {
                          DoInit();
                          return true;
                        }
                        else return false; //done, yay
                }
}

void BundleActivity::StartWork()
{
  OutputMsg(csString("Bundle activity started."));
  ScheduleNewPerform(MIN_DELAY);
}

void BundleActivity::WrapUp()
{
  bundleReady = false; //at the end of a step, bundle is not ready!
  char msg[1000];
  sprintf(msg, "%d bundles done, %d potential bundles left.", timesDone, timesToRepeat-timesDone);
  OutputMsg(csString(msg));
}

void BundleActivity::DoInit()
{
  bundleReady = false;
  recipeReady = false;
  bundlingStarted = false;
  if (timesDone == 0)
  { //get parameter if any
    WordArray words(cmd,false);
    if (words.GetCount()>0)	//it has a percent, so let's get this; default it is 100%
	percent = words.GetInt(0);
    else percent = 100;	//default

   //get setup
   csString recipeName = worldHandler::GetBrainItemName();
   if (recipeName.CompareNoCase(""))
   {
      Error();
      OutputMsg(csString("No blueprint equipped, please equip the bp for what you want to bundle and /bot bundle again."));
      return;
    }
    else
	rcp.setRecipeName(recipeName);

  containerEID = worldHandler::GetTargetEID();
  if (containerEID == -1)
  {
	Error();
	//actionToDo = CRAFTING_NONE;
	OutputMsg(csString("Kindly target (open) the container you want used for this craft run and then /bot craft again."));
        return;
  }
  containerID = containerEID.Unbox();
}

  //read recipe = EVERY TIME!
  psViewItemDescription out(CONTAINER_INVENTORY_EQUIPMENT, PSCHARACTER_SLOT_MIND);
  out.SendMessage();
  OutputMsg(csString("Reading the recipe..."));
}

Now we only need to actually add this new activiy to Foxybot so that we can call it. Save botactivity.cpp and close it. Open foxybot.cpp and add the following line in the constructor below the other similar ones (it's line 40 in my file):

activities[3] = new BundleActivity();	//adding the new activity to foxybot's "can do" list

Notice how Foxybot will know to delete it safely anyway since it deletes ALL the activities in its list in the destructor. But for that to work well we still need to let it know we increased the number of its activities. So save foxybot.cpp and then open foxybot.h and change:

static const int activitiesCount = 3;

...to:

static const int activitiesCount = 4;

That's it. Save foxybot.h and close it. Then simply re-compile the whole thing:

jam -aq client

Get yourself a drink while the thing compiles and then launch the client again. Simply drop a table or something, equip the bp you want, target (open/examine) the table and type:

/bot bundle 2000

Bot should start work on making 2000 bundles of whatever you have equipped in your character's mind. Drink that drink fast, for it won't take long!

February 24, 2017

Basic toolchain for Blender - Cal3d - Crystal Space

Filed under: Eulora — Diana Coman @ 1:20 a.m.

Only 2 days ago I was cobbling together a basic Cal3d+Crystal Space viewer of sorts for people to be able to test their Cal3d exported work to see if it's of any use for Eulora. Well, 2 days around here turns out to be a very long time indeed, as meanwhile it seems that we got not one but even two Blender to cal3d exporters actually working as well as at least one new animated sprite that could well wiggle its way on Euloran soil. To help along any artists who think they could contribute but feel that they don't have any sort of tools to support them, here's a basic toolchain in its first iteration. You'll need (DO mind the versions and don't update/innovate on this topic):

  1. Blender 2.66 - you are best off downloading sources and compiling them on your own system. If you are not enough of an artist to do that, at least get your binaries from the official Blender site and CHECK their checksum.
  2. Exporter from Blender 2.66 to cal3d:
    1. I tested Julien Valentin's exporter and it worked fine on a XUbuntu 12.04 64bits. So I'm hosting here the part you really need, check the sources page.
    2. To install it: simply drop the folder from that archive into the addons folder of your Blender 2.66 installation (you should find it if you go to your Blender folder and then further into 2.66/scripts/addons). Start Blender and go to User Preferences/Addons: either search for it or look in the Import/Export category. Activate the addon, save the preferences, enjoy. If all went fine, you'll find it as an option from File->Export->Cal3d Model (.cfg)
    3. To use it: load your .blend file in Blender; go to Object mode; select both main Armature AND the mesh (you can do this in a view of the object if you use "a" for "select all" OR you can do it from the Outliner). Go to File->Export->Cal3d Model (.cfg) and choose where you want it to spit out everything. The exporter itself says that there should be a second step as it allows you to prune bones that are not needed - in my experience it rushes through it all in one go (admittedly my test model had only 4 bones so nothing to prune for sure), but here it is mentioned, just in case you experience this second step. Go and admire your multiple xml files with funny extensions (.xaf, .xrf, .xmf, .xsf)
    4. Alternatively, give a go to a different (if similar) exporter that was tested and found working on Debian 8.
  3. Crystal Space and Cal3d: download and install according to instructions on Eulorum. NB: do NOT use any other versions of Cal3d and/or Crystal Space or your work will probably be useless for Eulora if you do.
  4. Bash script xml2bincal3d.sh to help you bridge the gap between what the exporter spits out and what cal3d+cs actually need. Note that it can help you best when it has in the same place a header.cal3d and a footer.cal3d files that it uses to assemble a working .cal3d file for your use with the cally3d viewer (see 5. below).
    1. To use it simply download it, make it executable and run it: ./xml2bincal3d.sh inputFolder [outputFolder] [pathToCal3D] . By default, the output folder is same as input folder and the path to cal3D is taken from environment variable CAL3DHOME (so just set that one if you don't want to use that last argument). Note that the path to cal3d IS NEEDED in order to actually run the converter between xml and binary cal3d formats. While you CAN run a similar converter that comes with your distribution, it will most likely NOT match the cal3d version that the exporter uses and it will miserably die on you.
    2. The script goes through all the .xsf, .xrf, .xmf and .xaf files in the inputFolder, calls the cal3d converter on them and stores the results in outputFolder/modelfiles. It also creates a file outputFolder/test.cal3d that includes all the exported skeletons, meshes and animations. NB: while this IS a valid file in general, you might want to check it and/or adjust values in there to match your needs!
  5. Viewer cally3d_args based on Cal3d+Crystal Space so that you can check your work. For sha512sum and more detailed instructions see the sources page.
    1. To use this, you'll need to compile it. A simple "make all" will do, provided you have already cal3d and Crystal Space installed.
    2. Once compiled, run as ./cally3d [file.cal3d] [animationName] The default values here are "test.cal3d" and "strut" respectively. To use your previously exported model you can:
      1. run the script at 4 above with the folder of this viewer as outputFolder OR
      2. manually copy the output from 4 to this folder
      3. give as argument to cally3d the full path to your .cal3d file. NB: in this case you WILL likely have to update manually the .cal3d file as well so that cally3d can find the skeleton/animation files.

When you finally run cally3d, you should be looking at your model performing whatever animation you asked it to perform. Note that the bash script gives animations their name based on the name of the .xaf file, so you might really want to follow the convention and name your animations in Blender as modelname_animationname rather than myAnimation sort of thing. If your animation is for instance wormOfDoom_knotting, then you'll be able to ask cally3d to show you the "knotting" animation of your worm of doom. Otherwise you can of course ask for anything you wish and get a load of errors.

As previously mentioned, this is a very basic toolchain cobbled together more as a prototype / starting point rather than a definitive reference. The most glaring current limitation (by no means the only one) is the lack of material support - whatever your worm of doom was wearing as its skin will be quite bluntly ignored by the viewer (though not by the exporter).

Enjoy! And if you complain, do it with concrete and precise descriptions of what hurts, where and what you did when it started. Unless you want it to hurt even more, that is.

January 23, 2017

Ordinary trouble of an Euloran explorer

Filed under: Eulora — Diana Coman @ 10:57 a.m.

(This could certainly be a whole category of troubles Foxy's bloody diary, if only there were categories to all the trouble.)
As a seasoned explorer of Eulora, maker of maps and of bots and of whatnots, Foxy knows where to search to find troublePetrified Bubbles, Dead Molluscs or other things of that sort. In a word, things that are worth 100+ times more than the basic stuff that can be found almost anywhere. For the naive explorer of words-other-than-Eulora, this might seem as the very opposite of trouble - the light, the profit, the happiness, the printing of money - in a word heaven without hell and earnings without losses or something of the sort. However, Eulora just doesn't work like that - it simply doesn't do all this "there shall be no trouble" thing and most probably it will never do it, not even when pigs1 fly all the way to the moon.

To illustrate this interesting2 lack of unbalanced happiness on Euloran soil let's consider for a second that you've got - as Foxy did - not only the rare knowledge of where Petrified Bubbles or Microgoats are, but also the concrete keys to corresponding claims3 for such resources. Such claims come in various sizes and increasing cost of building4:

Tiny claims take 1 Little Bit O' Nothing to build and so they cost 11 coppers.

Small Claims take anything between 1 and 7 Coarse Frangible Threads and so they cost between 180 and 1260 coppers.

Ordinary Claims take all sorts really, but adding them at base value reveals that they simply take about 15000 coppers, give or take some change. More precisely:

Ordinary claim of Petrified Bubble is 6*262+11*67+4*59+5*399+5*1963=14355.

Ordinary claim of Microgoat is 2*5439+2*1096+11*79+1484+73=15496

On the bright side, those two ordinary claims don't even require anything really rare - the PB claim takes some molluscs, but that's not a terrible issue. And the microgoat one takes one pacademia nut and otherwise plain and simple oil, almost as boring as it gets5. So then: what's the trouble?

The trouble is of course on the less-than-bright side, namely output and value and the likes. Out of the tiniest of claims (and thus for as little as 11 coppers), one will get at least one item. So one PB perhaps, meaning a whopping 43000 coppers and change. Or one microgoat with its rather macrovalue of about 86000 coppers. And that is only base value, as one can easily (sort of) get higher quality and therefore multiply those values by a factor of 2 or more. Small claims are especially good for this seeing how they easily allow overbuilding - the use of 7 threads instead of 1, thus pumping up the cost of building the claim but also the quality (hence, value) of output. For most resources *other* than those 2 discussed here, ordinary claims also quite imply overbuilding, since the 15000 coppers going in are certainly more than the 73 cost of one crumbly rock or even the more considerable 1963 cost of mollusc. However, for PB and Microgoats, ordinary claims are suddenly quite a pain to truly overbuild - at 86000 the microgoat and 15500 the ordinary bundle one needs 5.54 times the base value of the bundle (hence 554 quality points) to only match the smallest output at base value.

Sure, the above means that one is at least quite guaranteed a profit out of building an existing tiny/small/ordinary claim of PB or Microgoat. Assuming of course that there was no accumulated loss in *getting* such claim - quite a naive assumption. Nevertheless, the weirdness and trouble starts when one considers what quality of bundle should one make to build an ordinary PB or Microgoat. Should it be the lowest quality possible? That would mean apparently high profit as one PB is one PB and one Microgoat is really no smaller than another. However, a bundle of quality 1 will ALSO lower the quality (and therefore the value) of the output. Similarly, a high quality bundle will increase the quality of the output and given that each quality point of a PB is in fact worth 431 coppers and each quality point of Microgoat is worth 859 coppers, that's arguably not such a small matter. Adding to that the markup incurred on different ingredients required for the ordinary bundle, the whole thing suddenly becomes rather difficult to clearly decide on - and then one wonders simply why the ever loving fuck one got an ordinary instead of a plain old uncomplicated tiny or small - the result would STILL be 1 PB or 1 Microgoat, overbuilding the hell out of it would be waaaay simpler and more straightforward, underbuilding it would give no qualms and the trouble overall would be quite non-existent.

So then, can anyone come with any idea as to the actual usefulness of ordinary claims6 for high value items such as PB and Microgoat? Other than providing trouble, I mean. And perhaps the cuteness of an Ordinary Microgoat:

shot53


  1. They are called Multifunctional Samovars in Eulora for some reason 

  2. Some might call it painful, by another name. 

  3. Basically they would be the equivalent of veins I suppose - they mark the place where you need to build something to extract the actual resource - in unknown quantities, mostly small quantities, sometimes large and at rare times huge. 

  4. Will use base value throughout, to keep things simple at least for starters. 

  5. Boring IS good on Eulora - you don't want the sort of excitement that happens there 

  6. It's unclear - due to lack of data on the matter - whether the next size up for claims, namely Sizable would even do, but one can always hope it does and then go and dig. 

December 5, 2016

The Sentiments of Grass

Filed under: Eulora — Diana Coman @ 11:50 p.m.

(This could be - but nobody knows for sure anymore - another page out of that half-eaten Diary that Foxy used to keep. It all starts with pains and names and all that.)

Since its early days of broken Maths and painful cooking, Eulora has certainly grown up in leaps and bounds - or rather limps and hobbles. It grew up in fact so much that one might say it started quite developing - for starters it developed its own feelings and sentiments, dusts and mites, lint and horrors and all sorts that it calls with one word - numina.

Feelings1 in Eulora are quite abundant and totally worthless. At least the low quality, 100 a dime feelings that anyone can easily get - for Eulora's advances most pointedly include some rather ruthless Mathematics that apply to all that shiny new and totally petrified sentimental numina. Feelings of superior quality - superior in Euloran sense, aka "slightly less horrible than the usual" - though have recently been shown to be - of course - quite difficult to get. How difficult? Well, difficult enough that you'd spend about 200% their base-value2 worth to even get some, it would seem. Difficult enough that you'd first use all your 10 thoes to dig up some shiny, slinky, silky grass that WAS actually happy to be alive, then burn up the increasingly-rarer-and-hard-to-get coarse frangible thread blueprints, spend your days and nights hunched over a craft-table threading grass and then - only then - you'd hope to get some such lofty - despite being petrified really - feelings. As well as a hefty loss in terms of base value, of course - remember that ruthless Mathematics? It counts feelings with a big fat minus in front!

Perhaps feelings being so worthless in Eulora is hardly surprising given that people found before and in other parts that feelings are downright evil and out to get you when you aren't looking - at least when you aren't looking in all the right places, at the right thing and in your right mind that is. But even after discarding therefore those feelings in the dust where they can do least damage (to your finances at least), there surely are - there MUST be - the more valuable sort of numina, the Sentiments3. Oh, they certainly are more valuable - about 100 times more valuable. And almost impossible to get out of all that shiny, luscious grass that you've been... threading last night, full of feelings and what not.

Almost impossible, but... not quite. Rare indeed or rather worse than rare - meaning at a huge loss - sentiments it would seem can be had even of the grass variety. Welcome therefore to Eulora, this most sentimental island - tread with care for the grass itself is full of...feelings!4

 


  1. Tinkerer's Petrified Feelings, to be precise. 

  2. At least 200%! 

  3. By their full and very imaginative name - Tinkerer's Petrified Sentiments  

  4. And I don't know about your own feelings and all that, but if they are the usual Euloran type, they surely ARE out to get you, yeah. 

September 18, 2016

Happy 1st Anniversary, Foxybot!

Filed under: Eulora — Diana Coman @ 2:54 p.m.

While patting my reportedly very well-behaved bot on its neat and tidy state machines, I stumbled upon the proof of its inception in all its memorable words, one year and a few days ago:

diana_coman: and yeah, I am making progress on a bot to do it since yest; since I will never do that again by hand for sure

That promised progress was indeed made, that click-frustration was indeed channeled into code and one year later Foxybot tirelessly works in Eulora for everyone and anyone who can actually read. And for those who can't, it just waits there in the background with the endless patience that might simply be indifference all the same. It waits nevertheless one single command line away, always ready to help, never taking over. As far as I can see, its wait is long with many noobs, its usefulness is fully appreciated by the veterans1, its benefits are just as well reversed in the wrong hands. One player reportedly fucked up all his rather expensive exploration tools (chetty sticks) by setting up the bot with the wrong timeout - and not even checking on it apparently. In the player's own words: "Yep, i fucked up pretty gud". All in all, a happy bot existence if there ever was one.

Born out of frustration on the murky shores of euloran client code one year ago, Foxybot grew from a simple tinkerer of sorts to master of exploration too2. Some people suggested it might even be a deity of the land, worthy of a cult of its own. It cooked countless items in people's samovars and it tinkered all sorts on their craft-tables, in their bandar toolkits,  lapidary tables and what-nots. It made maps for Foxy, it found the famed Dead Molluscs, the infamous Slithy Tove and the rare Petrified Bubble. It brought exploration to a whole new level when it started using the craft-table to avoid mixing qualities and to work around the weight limitations of its masters. And close to its first anniversary, it made it into client release full and proper, a most useful and most used part of the game itself.

Happy first anniversary, Foxybot! May you always remain true to your best description so far:

mircea_popescu: people used to do this for a long time by hand lol

May you grow and learn forever more!

And as a present from Foxybot to all its euloran users, here's a fragment of an old, old map on which the secret hiding place of molluscs was first revealed:

tomb_of_molluscs Can you find out the coordinates of the purple spots that are the molluscs? There's a patch of shed snakeskins (top left) that might help you locate this on the larger map. Good luck!

 


  1. There are many examples scattered among the logs, such as this: "it's a huge boon for miners". And some reports of quite effective use by previously rather reluctant adopters.  

  2. In other people's words: "foxybot with pickaxes is fucking epic

August 4, 2016

Craft-Table Tourism with Foxybot 1.4

Filed under: Eulora — Diana Coman @ 11:31 p.m.

If you haven't yet seen Foxy dragging around a craft-table as part of her new drop/explore/build/take/move/drop ritual, it's only because you haven't been looking at Eulora lately1. So better go and grab yourself the latest client2, start compiling it and while it compiles, come back and read the story of the little bot that could do wonders with a humble craft-table. And yes, Foxybot 1.4 is now part and parcel of Eulora's client 0.1.2, hooray!

shot42

When Foxybot was as little as 1.3, it could happily explore for quite a while, but it horribly mixed items of different qualities and it inevitably got stuck when all it got was too heavy to even move any further. To avoid this, the whole explore got a few more steps of dancing with the craft-table. All you have to do is to make sure that the first table in your inventory is empty(first table, but not necessarily in the first slot - so you can have an empty table in the 10th slot, as long as in slots 1-9 you have anything *other* than a craft-table or just nothing) and then start the bot, for instance like this:

/bot explore 10000 line 10 1 7 7500 8500

The above means that the bot will do 10000 explore attempts, going in a line that is 10 attempts long, leaving the key in the claim after building (flip that 1 to 0 if you prefer to keep the keys), checking every 7.5 seconds to see how things are going and giving up on waiting (timeout) after 15 seconds (technically after 8.5, but because of 7.5 seconds between checks, it will be 15 seconds in total until timeout). More precisely, the bot will do the following:

  1. Get first table in inventory and drop it.
  2. Explore
  3. Build claim (if any)
  4. Take resources
  5. Put key in claim (if flag is 1)
  6. Move resources to the first slot in the table that is on the ground and *assign* this slot to that resource AND that type of claim (for instance: flotsam_tiny or flotsam_small)3.
  7. Move one step
  8. Pickup table
  9. Drop the table that was just picked up (hence NOT whatever other table might happen to be first now) and *repeat* from step 2.

The sequence above means that resources from tiny and small claims of the same type are kept separate. It also means that you really can explore for as long as you wish without becoming overweight, since everything is in the table and the table is just dragged on one step at a time (the movement is done when table is on the ground so there is no overweight issue). However, there are a few caveats, namely:

  • CaveAtAbundance: Like it or not, the craft-table has "only" 16 slots and each resource will take in the end two slots (1 for small and 1 for tiny). Hence, poor bot can fit only 8 different resources before it runs out of space in the table. Being a stubborn bot as it befits a machine, it will keep going nevertheless by keeping separated what it can (what is already in the table) and otherwise mixing (despite its best efforts) everything else. Hey, it's your bot, not your master to know what is what!
  • CaveAtPoverty: Bot works hard and finds claims, but claims are hungry for lbn and cft. Feed your bot appropriately with enough cft and lbn to last it or you'll come back to an inventory full of keys to tiny and small claims (mostly). You've been warned - have fun and ignore it, bot won't care either way.
  • CaveAtMeddling: The bot knows what it knows and nothing more - it's a state-machine! More to the point, if you start moving things around in its table, it won't know and it won't care - you are the master, you are free to mess things up - the bot will just keep doing precisely what it was doing. So if you for instance move some grass to its previously flotsam-tiny slot, then it will still try to move its next stack of flotsam into that slot. The server being quite uppity about it, will simply stack the flotsam instead into the next free slot in the table. In turn, this will cause further grief when the bot tries to put in *that* "free" slot something else and that gets moved further by the uppity server and so on and so forth. Hey, you wanted meddling? There's no better chance than this to turn it all into disgusting goop. Bon appetit!
  • CaveAtRestarting: If you stop your bot for any reason, be aware that you are probably better off by first stashing somewhere all resources found so far before starting the bot again. Otherwise the resources will probably mix horribly (server mixes what you take with anything in your inventory, including something that is *inside* a table). So either go with them to storage or dump them in a claim and lock it, but really, get rid of them, reset the bot (/bot reset) and only then start it again.

Some of the caveats above also have workarounds if you know what you are doing4. For instance, if you really have some stuff you want in that first table (maybe because it is too heavy and you can't move with it), you *can* still make it work fine: just put that stuff in the *last* slots in the table, since the bot will nicely start with the first slots. And make sure that you don't end up in the CaveAtAbundance dark corner, since there will be therefore space for even fewer than 8 resources.

Other than the above and as always, Foxybot is not only easy to change, but also easy to replace: simply replace any of its files or its whole folder with the corresponding files/folder of a previous (or your own) Foxybot version, as you wish. Recompile the client and you are all set, there is no incompatibility as such.

First users have already reported quite inventive uses in getting the bot to explore all the way to a mining spot. There certainly are others, so go bend the bot in new ways all of your own. Enjoy!


  1. And if you haven't looked at it until now, you already missed more than meets the eyes, but that's your loss, not mine. 

  2. Version 0.1.2b, as older versions won't work with the server freshly deployed this Wednesday. 

  3. For the curious but too lazy to read the code: this is implemented with a hashmap that has as key a string created from the type of the claim (tiny or small) and the resource's name and as value the number of the slot assigned in the table. This is because currently the client is dumber than the dumbest terminal and therefore there is no reasonable way for the bot to actually know what is in the table (short of asking the server *every* time). So the bot is basically blind to the world as it is, having only its own limited image of the world as it *should be* according to its previous actions. Feel free to discuss the Foxybot philosophy but keep in mind it's not even of school-age yet. 

  4. Better know - or find out - what you are doing at least half as well as the bot does. 

March 1, 2016

Coordinate Eulometry (or Tomb of the Dead Mollusc)

Filed under: Eulora — Diana Coman @ 12:15 a.m.

In the green and unforgiving world of Eulora, I am an explorer and mapmaker through necessity more than anything else (is there anything else?). As a result, I have made and even published maps and location of resources, but lo and behold, people want more! More of what? Well, more information and more useful things made by others, of course, what else. Excellent, you might say, they want it, you have it, sell it to them and everyone's happy, industrious and productive like never before. Perfect situation, perfect plans, perfect idea, except...do they want to pay what it takes? Not quite, no.

Given the above, here's where I become an explorer of potential deals and potential ways of pricing immaterial things in Eulora (euloran knowledge no less!) - I become one through necessity of course, more than anything else1. As if pricing of basic items was solved already, here I am, diving straight into attaching some price to knowledge, what can go wrong.

Considerations aside and going straight for the throat of the matter, here's how things stand: the current most-prized knowledge of resources is the location of the fabulous Dead Mollusc. Location which I'm the only one to know. This knowledge of mine is the result of painful and meticulous island-combing by Foxy over several months. Island-combing which was made possible in the first place only as a result of quite another piece of significant work - designing and implementing the bot itself. So then, do you think that all this would be worth 10 bitcents at least? Well, so far it doesn't seem to be worth much to eulorans at all.

Apparently, this unique, useful and really quite difficult (as in: expensive but also time-consuming and not trivial) piece of information is worth at most 3 bitcents or, perhaps, some parts of some mollusc cheese at the further cost of not-yet-found slithy toves. What to do then? Take the 3mn? Take the cheese and give some more unique and not yet found resources? Keep the secret of the molluscs' tomb? Since I don't find any of these options particularly good, here's a fourth: making concrete and potentially useful map knowledge available instead. Smaller pieces of information, smaller (arguably more digestible) prices, unknown, but potentially higher return. Judging by the long successful history of euloran official auctions of previously-unknown items, players quite like paying for unknown but potentially high return.

Here's what I have:

1. 100`217 distinct points2 at which I *never* found anything, despite trying between 1 (inclusive) and 10 (exclusive) times.
2. 1139 distinct points with 0 successful attempts and between 10 (inclusive) and 20 (exclusive) failed attempts.
3. 252 distinct points, 0 successful attempts, between 20 (inclusive) and 30 (exclusive) failed attempts.
4. 111 distinct points, 0 successful attempts, between 30 (inclusive) and 40 (exclusive) failed attempts.
5. 119 distinct points, 0 successful attempts, between 40 (inclusive) and 50 (exclusive) failed attempts.
6. 24 distinct points, 0 successful attempts, between 50 (inclusive) and 60 (exclusive) failed attempts.
7. 23 distinct points, 0 successful attempts, between 60 (inclusive) and 70 (exclusive) failed attempts.
8. 24 distinct points, 0 successful attempts, between 70 (inclusive) and 80 (exclusive) failed attempts.
9. 22 distinct points, 0 successful attempts, between 80 (inclusive) and 90 (exclusive) failed attempts.
10. 9 distinct points, 0 successful attempts, between 90 (inclusive) and 100 (exclusive) failed attempts.
11. 19 distinct points, 0 successful attempts, between 100 (inclusive) and 200 (exclusive) failed attempts.
12. 1 point, 0 successful attempts, 217 failed attempts.
13. 1 point, 0 successful attempts, 1320 failed attempts.
14. 1 point, 0 successful attempts, 2987 failed attempts.

Based on my exploration experience so far, I am fairly sure that some of the resources that haven't yet been found are precisely at the last 3 points above if not at some others in those packages there. However, based on my same experience so far, I am also quite sure that one needs either high enough rank or expensive enough tool in order to actually get those resources there where I missed repeatedly. So be warned that I have found before *different* resources in the same spot, which means that I wouldn't really trust 1 single successful attempt to fully tell me ALL that there is that one spot.

In any case, I'm really quite curious what eulorans think of the above 14 packages. Note that if I start from 1 copper per point at package 1, 10 per point at package 2 and so on, that still dwarves very quickly the 10mn per found (no surprises there) Dead Mollusc location.

As they say around here: looking forward to hearing from you!


  1. Honestly: is there ANYTHING else on Eulora? 

  2. Each point is identified through its x and z coordinates, considered as rounded integers. For example, point (15, 15) covers the small square from (14.6, 14.6) to (15.4, 15.4). If you are concerned about such granularity, do note that even the tiniest touch of a movement key that results in a barely perceptible change of position in game still bumps the coordinates by 0.1. 

« Newer PostsOlder Posts »

Theme and content by Diana Coman