This page is a discussion of the Trading and Commerce Module.
The Trading and Commerce Module is designed specifically to handle all in-game transactions involving the player, affecting the player's cash on hand and the contents of the cargo_contents dictionary for each of the ships in their fleet. To say "Trading and Commerce" is a Humna Humna-eqsue redundancy; both refer to the same function in-game. The basic architecture of the module is similar to other modules within the game that handle basic data types (such as integers and strings) and hash tables / dictionaries. This module is required by the Starport Module and the Planetary Exploration Module specifically, though there may be some other applications available in other modules. The Trading and Commerce module is vitally important to the game, as trade is the player's primary source of in-game revenue. All code for the trade module object as well as the required child classes is located in the trade.py file.
An instance of the trade module is intended to be loaded during the game's initial setup. This will create the game's trader object and load the game's master item list into memory. The module's internal trade list generation routines are called during the initialization of the Starport Module and/or the Planetary Exploration Module (in the event that an inhabited world is indicated when that module initializes). This routine will include a call to the Onomastikon Module to select an appropriate name for the trader involved. The trade list generation routines will use the internal item list to generate a list of goods that traders on the planet will be interested in selling to the player, and goods they would like to buy from the player (taking into account what the player has in stock). If the game indicates that the player is entering a trade center, control of the game passes to the Trade Interface. Items sold to the traders are removed from the player's cargo list, and vice versa. If the player exits the trade center, control of the game passes back to whichever interface belongs to the method that called it; the Trade Interface can be re-engaged by the player by entering a new trade depot. Should the game's clock roll over one day while on a planet's surface, new trade good lists for that day will be generated. The lists are cleared out prior to the Starport or Planetary Exploration Modules passing control of the game to a different module.
Data Structure of the Trader ObjectEdit
The following list is an indication of the various variables and methods that will be included in a Trader object. The following list is a rough indication of the various classes and variables that will be included. This is meant only as a guide; the names of the actual variables may be different from what's listed here (any changes to variable names should be updated here ASAP). Trader objects are meant to be used as direct objects, with no parent objects. Only one trader object is meant to be used in the game, and it is meant to be loaded by the core engine as the game is initially loading. Their data structure is as follows:
- Class: Trade
- Hash/Dictionary: Item_List
- Hash/Dictionary: Trade_Messages
- Hash/Dictionary: Trade_Data
- Hash/Dictionary: Trade_Lists
- Class: Current_Trade
- String: szDateCreated
- Flag: bBarred (default to "False")
- Flag: bTechLocked (default to "True")
- String: Species
- String: Trader_Name
- String: Tech_Level
- String: Econ_Level
- String: Trade_Style
- List: Trader_Buys
- Class: Trade_Good
- List: Trader_Sells
- Class: Trade_Good
- Integer: Emotion_Index_Value
- Integer: Frustration_Level
- String: Frustration_Color
- String: Current_Item
- Integer: Player_Price
- Integer: Player_Last_Price
- Integer: Current_Offer
- Method: n_EnterCenter()
- Method: conductTrade(nItem, fBuy)
- Method: wheelAndDeal (nOffer)
- Method: _szHackAndSlash(szMsg)
- Class: Current_Trade
- Method: __init()__
- Method: makeLists(nXpos, nYpos, nFI)
- Method: cleanOut()
Data Structure of the Trade Good ObjectEdit
The following list is an indication of the various variables and methods that will be included in a Trade Good object. The following list is a rough indication of the various classes and variables that will be included. This is meant only as a guide; the names of the actual variables may be different from what's listed here (any changes to variable names should be updated here ASAP). Trade Good objects are meant to be used as direct objects, with no parent objects. Trade Good objects are meant to be created any time an item needs to be added to the player's inventory (usually when an item is bought or picked up on a planet's ssurface), and are meant to be created on the fly as the game progresses. Their data structure is as follows:
- Class: Trade_Good
- String: Name
- String: Category (default to "Curio")
- String: Tech_Level (default to "Stone Age")
- String: Type (default to "None")
- String: Description (default to "N/A")
- Float: Unit_Volume
- Integer: Current_Price
- Integer: Best_Price_Point
- Integer: Standard_Trade_Value
- Integer: Initial_Price
- Integer: Boundary_price
- Enum: Trade_Status (NOT_BARTERED, BARTERED_ONCE, BARTERED_TWICE, BARTERED_THRICE, BARTERED_FOUR_TIMES, DONE_BARTERING; default to NOT_BARTERED)
- Integer: nMaxAmount (default to 10000)
InterfaceEditThe trade interface consists of four separate areas, as demonstrated in the graphic:
- Message Box
- Image Box
- Info Box
- Interface Buttons
The largest of the six interface areas is the Message Box, which is located in the large area on the right-hand side of the screen. This is where the game displays all text information being sent to the player, including greetings, trade-related messages, and trade good lists. Text sent to the Message Box remains visible to the player unless an action occurs that clears out the box (such as concluding a transaction), or until enough text has been sent to the Message Box to require it to "scroll down". If a message sent to the Message Box is long enough that it would scroll down before the player has had an opportunity to read all of it, the message can be set to "pause" mid-way through its display (this should never happen, but which we can account for anyway since a similar will be needed for the Starship Interface's Message Box).
The Image Box is located in the upper-left-hand side of the screen. This area is used solely for the display of images, and can either display a static image of the trader or an image of the trade good in any ongoing transaction. The image shifts to the trade good in question once trade begins on that good, and switches back to an image of the trader once the transaction is complete. To fill this area and display properly, all images related to trade should be standardized to the same overall size, and preferably to the same file format (as of this writing, the desired file format is .PNG, with a resolution of 260x190 at 300 dpi.)
Located immediately below the Image Box is the Info Box. This is an auxiliary text area that contains such information as the name of the good being bought or sold, the type of transaction that is taking place (either buying or selling; this is from the player's point of view), the STV percentage of a selected good (note that this can be displayed prior to actually initiating trade on an item, to give the player a better sense of what kind of deal they ultimately get out of a given transaction), the trader's current offer for that good, and the player's current amount of available funds. An extension of this area exists in the bar across the top of the screen, where the name of the race is displayed, followed by the name of the individual trader (as generated by the Onomastikon module).
Finally, the Interface Buttons are located at the bottom of the screen. There are three large buttons in this area, labeled "Buy", "Sell" and "Exit". They may be activated one at a time in one of three ways: by clicking on them with the mouse, by using an appropriate hotkey for the desired menu (default hotkeys are F1 to F3 inclusive on the keyboard, with F1 corresponding to Buy and each successive key to the right corresponding to the next button to the right), or by using the arrow keys followed by the <Enter&rt; key (by default, the "Buy" option will be highlighted upon entry to a Trade Depot/Center; the left and right arrow keys will move a cursor, which will highlight the next option left or right, respectively. The cursor will scroll, so pushing the left arrow key when the cursor is on the "Buy" button will move the cursor to "Exit", and vice versa). The buttons are displayed at all times when a specific transaction is not taking place while the trade interface is still active. When selected, a specific button will turn red. If "Buy" or "Sell" are selected, the appropriate trade good list is displayed in the Message Box, the ACKNOWLEDGED tone will sound, and the selected button turns red; when a Trade Good List is active, a player may select a trade good to begin a transaction either by double-clicking on it with the mouse, or by scrolling down to it with the arrow keys and pressing the <Enter&rt key. When a Trade Good List is active, pressing the <Space Bar&rt; will de-activate the trade good list and clear the Message Box, returning control to the Interface Buttons If "Exit" is selected, the button will turn red, the ACKNOWLEDGED tone will sound, and a farewell message will be displayed in the Message Box. After two seconds, the trade interface will pass control back to whichever interface called it (the Starport Module or Planetary Exploration Module).
Once the player begins a transaction, the three Interface Buttons change to "Accept", "Counter" and "Refuse". These buttons are activated in the same way as the buttons in the main portion of the interface (by mouse click, hotkey or manual activation). Selecting "Accept" sounds a CHI-CHING tone, sets the Trade_Status enumerator for the current trade good to DONE_BARTERING, and activates a set of controls in the Info Box that allows the player to select how much of the current trade good they wish to buy or sell. Clickable arrow keys (a "quantity selector") will be provided on either side of the specific amount to adjust it by amounts of 10, 1 and 0.1, either up or down. The player will also be allowed to click on the amount and type in a specific amount. Finally, the player will be able to use the up and down arrow keys to manually enter an amount; these keys will default to an increment of 10 (the shift key can be pressed to change the increment to 1, pressed again to change it to 0.1, and again to switch it back to 10, with a repeating cycle). The final amount can be set by pressing the <Enter&rt key. Once set, the message "DOING TRANSACTION" will appear in the Message Box. Should the player select "Counter", the ACKNOWLEDGED tone will sound and an interface will appear in the Info Box that will let the player enter a counter-offer for the current transaction. This interface will work the same way as the interface for selecting specific amounts of goods. Upon entering a final amount, the game will call the trader object's wheelAndDeal(nOffer) method (for details, see the method's description on this page). Selecting the "Refuse" button sounds the NEGATIVE tone and also calls the wheelAndDeal(nOffer) method; no further interface controls will appear when this option is selected. At the conclusion of the current transaction, regardless of the outcome, the Interface Buttons will revert back to "Buy", "Sell" and "Exit", with whatever button was active when the transaction began still activated.
The Trade object's initializer begins by declaring the three holder dictionaries for various pieces of data included in a trade, namely the trade goods, messages and disposition data. It then creates an XML parser for the class and tells the parser to ignore XML namespaces (this is used to ease the accessibility of the XML data). Once that's done, the method creates a SAX content handler object for each of the three lists; for each list, the SAX handler object simply seeds the holder dictionaries with the necessary data. The initializer for each SAX handler object looks for various element types relevant to the list it's asked to parse (specific details of which handler parses which data and how it's done will be added to this document when coding begins). The initializer uses each SAX handler object in turn as a content handler and parses the XML files, which does the actual loading of data into the various dictionaries. Once the dictionaries are seeded with the necessary information, the initializer routine is complete. The Trade object will remain in active memory until the game routine is killed.
makeLists(nXpos, nYpos, nFI)Edit
This method's purpose is to prepare a trade list for both purchasing and selling on a planet's surface or at a Starport. The method is called either by the Starport or Planetary Exploration Module at the time the player either begins a new game (naturally, for this instance, the method is called after the initializer is called), lands at a Starport, or lands on a planet. When that event occurs, the makeLists routine checks the player's current coordinates against the keys of the hTrade_Data dictionary. If there is no match, the routine does not go any further; there are no trade depots at the player's location and trade will not be possible. Control of the game passes back to the calling function at that point. Next, the routine checks to see if there are already existing trade good lists for that location; if so, it will not go through the full creation process. Instead, the routine will seed whatever data is necessary to make the pre-existing trade good lists current.
Otherwise, the routine will create a current_trade object and seed it with the information it finds; the szSpecies string will be set to the species name, szTech will be set to the technology level, szEcon will be set to the indicated economic level, and szStyle will be set to the indicated trading style. For all items on the species' "willbuy" list, a Trade_Good object is created and appended to the hTraderBuys dictionary, using the good's name as the key and the remaining data for its value. Similarly, all goods on the "willsell" list will be created and appended to the hTraderSells dictionary, except that the game will also produce a maximum amount that the player can buy (between 20 and 100 cubic meters) and set the result equal to the nMaxAmount integer for that good. Note: At this time, the game will be programmed to bypass this step for goods being sold by the trader, instead setting no maximum amount. Trading a hold full of trade goods has proven to be a good way to turn a fast buck in SFRPG playtests; considering the prices of some of the equipment in the game, the player will need to make money quickly. If it proves to be unbalancing, maximum purchase amounts should be able to be reinstated pretty easily.
The module will then select a random number from one to ten and select the indicated number of standard trade goods from its hItem_List dictionary at random. After selecting goods, the module will further evaluate its selections, removing any duplicated selection and any trade good that is above the technological level indicated by the Current_Trade object's szEconLevel string. The remaining items will have Trade_Good objects created for them, which will be appended to the hTraderSells dictionary. As with the items currently existing on the list, a maximum amount of that good from 20-100 cubic meters will be set prior to being appended to the list (again, this will be bypassed for the time being). Finally, Shyneum will be appended to the list; the prices will be set at this time depending upon the amount set by the Event Handler (see the discussion of the Event Handler for details, and a maximum purchasable amount will also be set.
Once the items for sale have been set, the routine will go through a similar procedure for standard trade goods to see what goods the trader would like to purchase, with the final results (after removing duplicates and advanced goods) appended to the hTraderBuys dictionary. Minerals and Curio artifacts that the trader would like to buy are also selected at this time, using a similar method. Once this list is set, it is compared against the contents of the cargo bays of the player's ships and vehicles. Any good that is not possessed by the player is removed from the list; for all goods that are possessed by the player, the maximum amount that the trader will purchase is set to the total amount of the good in question that the player possesses.
With both lists built, the method will then set about filling in the price data. Three prices must be set for each good at this time: the best price point (the price the trader wants to pay for the good), the initial offer price (the price the trader will initially propose to the player, which is always a better deal for the trader than the best price point), and the boundary price (the price above or below which the trader will balk). The best price points are set first; these are based on the STV of the good, the type of the good, and the economic level of the planet. For specialty trade goods, the best price point is set to an amount between 50-120% of the STV if the good is in the hTraderSells dictionary, and between 120-200% of the STV if it's on the hTraderBuys dictionary. For all other goods, the amount is set between 50-100% of the STV if the economy is Depressed, between 70-130% if the economy is Level, and between 100-150% if the Economy is Inflated; the amounts hold true regardless of whatever list the item is on. Initial offer prices are set next. These are set based on the trader's style and what list the item is on. If the trader's style is "No Bartering", the initial price is set equal to the best price point price, as is the boundary price. If the trader's style is "Bargains a Little", the initial offer of the good is set between 75-85% of the STV if the item is in the hTraderBuys dictionary and between 125-150% of the STV if it's in the hTraderSells dictionary. If the trader's style is "Bargains a Lot", these values are changed to 50-75% and 150-200%, respectively. Boundary prices are set last. As with the best price point and the initial offer price, the boundary price is dependent upon the STV of the good and the planet's economic level. For depressed words, the boundary price is between 5-45% of the STV for goods in the hTraderSells dictionary and 105-150% of the STV for goods in the other dictionary. These values change respectively to 20-65% and 135-180% for Level economies, and 50-95% and 155-200% for Inflated economies. Once these prices are set, an additional adjustment of all prices by 25-40% of their respective STVs in the players favor will occur should the trader's "color" be green at the onset of trade.
Once both trade lists are complete, an entry corresponding to them is created and added to the Trade object's hTrade_Lists dictionary, and control of the game passes back to the calling function at that point.
This method's purpose is to pass control of the game to the trade module when called, and to return game control back to the caller once the trade sequence is complete. This method can be called whenever the player enters a Trade Depot at Starport, or when they place their vehicle in an adjacent zone to a trade center on a planet's surface (if the player does this, they will be asked by the Planetary Module if they wish to trade; the module will be called if they indicate they would like to do so). When called, the method switches interface control over to the Trade Interface. It then checks the data that was seeded by the makeLists(nXpos, nYpos, nFI) method and seeds the name of the species and places that information in the bar at the top of the screen (the extension of the Info Box); a call to the Onomastikon module is made at this time to get a name for the trader, seeding that information into the Current_Trade object's szTradeName string; this information is then displayed in the same area as the species name. The Player object's Available_Funds integer is then called and that information is placed in the Info Box. The module then creates a boolean called fTradeOkay and sets it to False.
Next, the module searches the Player object's Emotion_Indexes dictionary to find the race that corresponds to the one seeded by the makeLists(nXpos, nYpos, nFI) method, and retrieves the Emotion Index for that race. If the current Emotion Index is in the Fight/Flight range (for details, see the discussion under the Communications Module), the module will retrieve a "fed up" message from the Trade Object's Trade_Messages dictionary and pass it along to the _hackAndSlash() method for display; once it receives the message back, it will display the message in the Message Box. It will also check the bBarred flag on the Current_Trade object that was seeded. If that flag is set to "True", it will do the same thing (display a "fed up" message).
Otherwise, the module will retrieve the player's current coordinates. Using that information, the routine searches for the message that corresponds both to the race and the current location, and passes it along to the _hackAndSlash() method for display, setting fTradeOkay to True; once it receives the message back, it will display the message in the Message Box. Finally, regardless of its present status, the Current_Trade object's fTechLocked flag is set to True.
Provided that fTradeOkay is True, the method enters a while loop, keeping control over the main Interface Controls. If either "Buy" or "Sell" is selected, the method recalls the appropriate trade list and displays it in the Message Box, sorting the list by artifacts, then specialty, than standard, then lifeforms, then minerals prior to display. Items in the hTraderBuys dictionary that are not in the player's cargo bay are not displayed; items in the player's cargo bay that are not on that list are ignored (as opposed to showing them with a "zero" offer, as was done in SF2). Only the item's name and either the initial offer price or finalized price is displayed (depending on whether or not trade has been finalized for that good already). A local fBuy flag is set to True if the player is buying and False if the player is selling. When an item from the displayed list is selected, control over the Message Box display is passed to the conductTrade (nItem, fBuy) method and a transaction begins. When the conductTrade method goes out of scope, the method will check the fBarred flag of the Current_Trade object; if it's still set to False, nothing happens. Otherwise, fTradeOkay is set to False and the loop is broken. If the player selects "Exit", fTradeOkay is set False and the Current_Trade object's szFrustration_Color string is checked; if it is "red", nothing further happens. Otherwise, a "thanks" message will be displayed in the Message Box.
After leaving the loop, the game will wait two seconds; the method will then return 0 to its caller before going out of scope.
conductTrade (nItem, fBuy)Edit
This method's purpose is to supervise the actual trade of an item. When the method is called by the n_EnterCenter() method, the arguments passed dictate what item is being traded and will set the "action" level for the trade ("sell" or "buy"). The method will take the name of the corresponding item from the trade list and will seed that data into the Trade object's szCurrent_Item string; likewise, the item's Initial value will be seeded into the Trade object's nCurrent_Offer integer. It will then check the item's hTrade_Status enumeration for its value. This value should be between -1 and 4, with the number corresponding to the number of attempts made to close trading on the item by the player (a value of -1 indicates that a successful final price has already been set). If the price has already been set, the routine goes immediately to quantity selection (see below).
Otherwise, the routine checks the item's category. If the item's category is "technology", it will then check the status of the bTechLocked flag. If that flag is True, the routine will select a message from the "techlock" category to display and send it to _szHackAndSlash for evaluation. Upon return, the method will display the message in the Message Box, then go out of scope and pass control back to n_EnterCenter(). If any one of these conditions is not true, the routine will scan through the trade object's hTrade_Messages dictionary and selects at random a message from one of the appropriate categories (open, second_try, third_try, fourth_try, or fifth_try) depending upon the item's hTrade_Status value (0=open, 1=second_try, 2=third_try, 3=fourth_try, 4=fifth_try). After displaying the message in the Message Box, the routine will do one of three things, again depending upon the hTrade_Status value. If the value is 4, the routine will select a message from the "fifth_try" category and send it to _szHackAndSlash for evaluation. Upon return, the method will display the message in the Message Box, set the Current_Trade object's bBarred flag to True, wait two seconds, then go out of scope and pass control back to n_EnterCenter(). If the value is 2 or 3, the routine will select a message from the "third_try" or "fourth_try" category (respectively) and send it to _szHackAndSlash for evaluation. Upon return, the method will display the message in the Message Box, then go out of scope and pass control back to n_EnterCenter()
If the value is either 0 or 1, the routine will pass control to the Interface Controls, switching them over to "Accept/Counter/Refuse" mode. If the value is 1, a value of 2 is added to the nFrustration_Level integer, otherwise it remains set as is. If the player selects "Accept", the routine locks the current offer in as the value of the Trade Good in the appropriate trade good list, and change its hTrade_Status value to -1 (DONE_BARTERING). The routine will then do a special evaluation of the opposing trade list (e.g. if the transaction took place using the hTrader_Buys list, the evaluation will be of the hTrader_Sells list). If the same trade good is found on that list, the best price point of that good is immediately adjusted to the amount set in the transaction, and all other prices are adjusted as well (this prevents the player from making money inside one trade center without leaving, and resolves the infamous Starflight 2 trade bug). The routine then goes immediately to quantity selection (see below).
Should the player select "Refuse", the routine will call the n_WheelAndDeal method, which will evaluate whether or not trade will continue. Regardless of the returned message type, the routine will add two to the Current_Trade Object's nFrustration_Level integer. Should the n_WheelAndDeal method indicate that a "player_refuse_end" message be displayed, the routine will pass the message to the _szHackAndSlash(szMsg) method, display the returned message, change the enumeration value for the trade good in question (adding one to the value), add one to the wait two seconds, and then switch the Interface Controls back over to "Buy/Sell/Exit" mode. If a "player_refuse_cont" message is indicated, the routine will call the sz_checkColor() method. Should it return "red", the method will override and show a "player_refuse_end" message instead, with the same overall effects. Otherwise, the module will calculate a new value for the Trade Good Object's nCurrent_Offer integer; the new value will be between 50-70% of the difference between the current value of the integer and the best price point in the player's favor. Control then goes to the Interface Controls.
If the player selects "Counter", a dialogue for them to input their next offer will enter in appear in the Info Box, which will then also appear in the Message Box as soon as they press the Enter key (which locks in their offer). The nPlayer_Last_Offer integer is set equal to the nPlayer_Offer integer, and then the nPlayer_Offer integer is set equal to what the player has just entered. After displaying a message for the player's offer, the routine will call the n_wheelAndDeal method, using the player's offer as an argument. The module's behavior will depend largely on the signals passed back to it from n_wheelAndDeal.
If a "refuse" message is sent, the method will call the sz_checkColor() method. If it returns "red", the method will override and display a "refuse_final" message instead, setting a local bFinalWarning flag to True (this flag, if set to True, will override all message types with an "angry_end" message if the player does not accept the offer on the next go around; this outcome adds one to the value of the species' Emotion Index). In all cases, after this type of signal is received, the method adds one to the value of the nFrustration_Level integer.
If a "counter" message is sent, the method will check the signal sent to it by the n_wheelAndDeal method. Depending on that signal, it will calculate a new offer, which will be either ten or twenty percent of the item's STV in favor of the player. This new offer is then compared against the player's offer and the item's best price point. Should the new offer be "worse" for the trader than the player's offer, the method will override and show an "accept" message instead, with the same effects as if an "accept" message had been signalled (see below). Otherwise, if the new offer is above or below the best price point (depending upon the transaction taking place), the method will override its calculation and set the best price point for the new offer. This will set an internal flag that will override all other message types with "refuse" messages, with the same effects as if a "refuse" message had been signaled as described above.
If an "accept" message is sent, the method will lock in the current offer in as the value of the Trade Good in the appropriate trade good list, and change its hTrade_Status value to -1 (DONE_BARTERING). After evaluating the other trade list for the same good (as what occurs when the player accepts the trader's offer), the method will reduce the value of the nFrustration_Level integer by one, and then move on to quantity selection.
In all cases, once a final message type has been selected, the method will retrieve a message of the indicated type at random from the hTrade_Messages dictionary and pass it over to the _szHackAndSlash method. When control passes back, the final message assembled by _szHackAndSlash is displayed at the bottom of the Message Box after a short delay since the previous message had been displayed (around two seconds).
Whenever the player selects "Accept" from the Interface Controls or the method selects an approval message, the quantity selection controls appear. In all successful transactions, the player is limited by the amount they can buy from a trader by the available amount of space in their fleet’s cargo bays, the amount of cash they have on hand, and the amount of the good the trader has on hand. They are limited in the amount they can sell to the trader by the amount of the good they have on hand. As the player uses the controls, the method will check the current amount of the good they've entered into the quantity selector; if the amount indicated is more than the trader has on hand (when buying) or if it is greater than the amount of the good the player has in his cargo bays (if selling), the message "NOT ENOUGH GOODS AVAILABLE" will appear below the quantity selector; the NEGATIVE tone will sound and the selector will zero out if the player attempts to lock in the amount at that point. If the player is buying, the routine will also check the total amount of remaining space available in their fleet's cargo bays; if the player exceeds the amount of cargo space available, the message "NOT ENOUGH CARGO SPACE AVAILABLE" will appear below the quantity selector; the NEGATIVE tone will sound and the selector will zero out if the player attempts to lock in the amount at that point. Finally, the method will calculate the amount the player will have to spend to buy as much of the good as they've indicated (if buying); this amount will be displayed beside the quantity selector. If the indicated amount would make the player go into debt (i.e. have less than zero MU), the message "NOT ENOUGH FUNDS AVAILABLE" will appear below the quantity selector; the NEGATIVE tone will sound and the selector will zero out if the player attempts to lock in the amount at that point.
Once the player has indicated an amount that is within their price and cargo considerations, the CHI-CHING tone will sound and the message "DOING TRANSACTION" will appear in the Message Box. The method will add or subtract the amount calculated from the Player Object's nAvailable_Funds integer, will add an entry to the Player Object's vTransactions list (after assembling the data into the necessary tuple; the transaction description will either be "SOLD-" or "PURCHASED-" depending on the transaction type and will be followed by the name of the good traded) and will either add or subtract the indicated amount of the good in question to/from the hCargo_Contents dictionary, as well as the fCargo_In_Use floats of the various ships in the player's fleet (if adding, the player's flagship will be filled first, followed by the ships in their fleet in the order in which they were added to the fleet; when subtracting, this order is reversed).
If the player is selling (i.e. if the trader is buying), the method will make check the name of the item in the transaction against the "willbuy" data for the species in the Trade object's hTrade_Data dictionary. If the good is there, a random message from the "techunlock" category will be displayed in the Message Box, a point will be deducted from the species' Emotion Index, the Trade_List object's bTechLocked boolean will be set to False (if it isn't already), and the Frustration counter will be reset to zero.
After all these final calculations have taken place, the method switches the Interface Controls back over to "Buy/Sell/Exit" mode and finally passes control back to n_EnterCenter().
At all points in this method, if the nFrustration_Level integer is indicated to be adjusted, a call to the sz_checkColor method immediately follows. sz_checkColor is also called after any action that concludes trade occurs (after the trader makes a final refusal of the player's offer, or at the successful conclusion of a trade).
This method's purpose is to adjust the Frustration Index during trading sessions and to report on any changes in the "color level" of the trader (the terminology is based on the Psychic Probe artifact, which originated in SF2 and whose purpose was to inform the player when they had just about pissed the trader completely off. SF3 and any descendant games may want to incorporate other artifacts with the same effect, but the same terminology will be used.) The method is called either by the conductTrade() method or the n_wheelAndDeal (nOffer) method any time a call for an adjustment to the nFrustration_Level integer is mentioned; this method does the actual adjusting, with the amount of the adjustment equal to the nAdjust argument passed to the function. A local szColor string object is created when the method is called, and set to a default value of "yellow".
If nAdjust is not equal to zero, the method will then check the Current_Trade object's szTrade_Style string and the new setting of the nFrustration_Level" integer. If the indicated style is "No Bartering" and the frustration level is 4 or higher, or if the style is "Barters a Little" and the frustration level is 5 or higher, or if the style is "Barters a Lot" and the frustration level is 6 or higher, it will set szColor string to "red". If these conditions are not filled, it will next check the status of the Current_Trade object's bTechLocked flag. If that flag is False and the nFrustration_Level" integer is below zero, it will set szColor to "green". Otherwise, it's set to "yellow". Once the color level has been determined, the method will check szColor against the value of the Current_Trade object's szFrustration_Color string. If they match, the method does nothing further.
If, on the other hand, they do not match, or if nAdjust is set equal to zero), the method will check to see if the Player object's bProbe flag is set to True. If it is not, the method simply sets the szFrustration_Color string's value equal to szColor and goes out of scope. If it is, however, the method will scan through the player's cargo bays in search for the item that set the bProbe flag True, and will retrieve from that item the color message that matches the new szFrustration_Color value. After it has been retrieved, the method will display it in the Message Box before going out of scope.
This method's purpose is to evaluate an offer made by the player. This method is called by the conductTrade (nItem, fBuy) method any time the player enters a new counter-offer for the price of a good or any time they reuse an offer. The amount of the offer is passed to the function as an argument, with a value of zero passed should the player refuse an offer. When called, the first thing the routine will do is an opposed Negotiate Check. The routine will retrieve the value of the Communications Officer's Negotiate Skill score as well as their Communications Discipline score, and will add together the Negotiate score to one-tenth the value of the Communications score (rounded down). To this amount, a random integer from 1-100 is added. The player's Fleet CSSV is also retrieved and compared to a table to garner the relative Difficulty Level. Next, the routine will look up the information in the Trade object's hTrade_Data dictionary and retrieve the skill score for a trader of the involved species (to which their relative Communications Discipline bonus has already been factored in), adding a random integer from 1-100 to that amount. If the player refused the offer, the routine will check to see if a specialty trade good is involved with the current transaction. If so, an extra 50 points are added to the trader's value; only 25 points are added otherwise. The two values are compared in order to set the position of a local fCheck flag; the flag is set to True if the Communications Officer winds up with the higher value and False if the Trader winds up with the higher value.
Next, the routine will check to see if the player refused the offer. If so and fCheckis True, the routine will return a value signaling the conductTrade method to show a "player_refuses_cont" message. If the player refuses and fCheck is False, the routine will signal the conductTrade method to show a "player_refuses_end" message instead (this will effectively end trade, as discussed above). If the player has not refused the offer, the routine will compare the offer to the various values associated with the good currently being traded.
If the current transaction is sale (i.e. if the player is buying the item), and their offer is greater than or equal to the best price point, the routine will signal conductTrade to show an "accept" message. If fCheck was True in this case, the routine will subtract one from the Trade Object's nFrustration_Level integer. If the player's offer is less than the best price point but greater than the item's STV, the routine will signal conductTrade to show a "counter" message; a signal will also be sent to make a larger price reduction if fCheck is True. If the player's offer is less than the best price point and STV, but still above the boundary price, the routine will check fCheck; if it is true, the signal for a "counter" message will be sent, and the routine will add one to the Trade Object's nFrustration_Level integer. If fCheck is False, the routine will send a "refuse" signal to conductTrade. Finally, if the player's offer is less than the boundary price, a "refuse" signal will automatically be sent. The value evaluations and associated message signals are reversed if the current transaction is a purchase (i.e. if the player is selling the item).
Once the signal is sent, the method goes out of scope; conductTrade adjusts offer prices and the frustration counter, selects the specific message to be used, and routes that message to the _szHackAndSlash method.
This method's purpose is to scan messages selected during the trading process for "filler" characters and to replace them with the appropriate content. This method is called by the conductTrade method after that method has selected a message to display to the player. The method scans through the entire length of the message for a pre-set list of characters (^, @, %, *, #, and |) using a while loop (using a fScan flag set to False as the escape condition). When one is found in the message, the method will check for an escape character by checking to see if the previous character was a backslash (this will let us still have these characters in a trade message, if we so desire). If so, the backslash is replaced by a space and the character is ignored. Otherwise, the message is "popped" up to that point into a return string. The preset character is replaced with the appropriate information: "^" with the player's current offer, "@" with the player's previous offer, "%" with the trader's current offer, "*" with the name of the good in the current transaction, "#" with the name of the player's Captain character, and "|" with the trader's own name. This information is also appended into the return string. The position of the pre-set character is logged and used as a reference point for "cutting" the remainder of the message should another pre-set character be encountered. The process is repeated until the entire message has been scanned through. At that point, any remaining portion of the message is appended to the return string, and that final string is returned to conductTrade before the method goes out of scope.
This method's purpose is to check all existing trade good lists to see if they've "expired", which occurs after five in-game days have passed since a given list was created. This method is called by the Event Handler after a full game day has passed. The method scans through the keys of the hTrade_Lists dictionary and compares the given date against the szDateCreated string for all keys in the hash table. If it finds an expired trade good list, the method checks to see if the coordinates associated with the list match with the character's current location. If they don't, the game simply deletes key from hTrade_Lists, removing the data. If, however, the coordinates do match, the makeList method is called immediately after the old data is deleted, immediately creating a replacement list. Once complete, control passes back to the caller.
The Trader object, like most of the other objects in the game, is largely XML driven; this will help keep it flexible up until SF3's design is finalized (i.e. goods of various categories can be added and removed from the game freely, based upon what data is available in the XML files). The trader object requires three different XML files, one containing the list of the goods in the game (the file "items.xml"), one containing the trader messages in the game (the file "trade_messages.xml"), and the third containing the list of worlds in the game at which trade can be conducted (including what items should be available for purchase and sale there, as well as the disposition of the traders at that world; the file is "trade_data.xml"). The following briefly goes over what data is located in these XML files.
The file "items.xml" is designed as a master list of items that will be in the game. Under the root element, entries in the XML file may be under of one of the following categorical elements: "curios", "devices", "other_artifacts", "technologies", "mission_items", "trade_goods", "lifeforms", or "minerals". Actual entries are empty sub-elements under each of these categories; the element name for the first four categories is "artifact", and each entry contains a "category" attribute to specify the type of artifact involved: "curio", "device", "mission", "other", or "technology". Technology goods may or may not be empty elements; if they are not, they can only contain "color" subelements (used by the checkColor method to convey trader frustration status information to the player), which must have three attributes: "green", "yellow", and "red". Trade goods either use the sub-element name "stg" or "specialty"; "stg" (shorthand for standard trade goods) must have an "era" attribute with one of four values: "Stone Age", "Metal Age", "Industrial Age", or "Starfaring Age". If an era attribute is not provided for a Standard Trade Good, the program will automatically assume the good is "Starfaring Age". All entries, regardless of categorization, need at least three attributes: "name" (which specifies the name of the item), "stv" (its unit value in MU), and "volume" (the size of a unit of that item). All entries may be given a "type" attribute for further differentiation. The program will look for type "contraband" under all entries (denoting materials that are illegal for the player to possess), and will look for "fuel" and "repair" under minerals (for those types of minerals, respectively).
Sample structure of the items.xml file:
‹?xml version="1.0" encoding="UTF-8"?› ‹!–– Note: STVs prices are per cubic meter for all items ––› ‹root table-name="items" version="0.2"› ‹curios› ‹artifact category="curio" name="A Mulligan" stv="10" volume="0.1"/› ‹/curios› ‹devices› ‹artifact category="device" name="Black Egg" stv="125000" volume="0.1"/› ‹/devices› ‹other_artifacts› ‹artifact category="other" name="Interesting Item" stv="22300" volume="205.5"/› ‹/other_artifacts› ‹technologies› ‹artifact category="technology" name="Cargo Concealer" stv="50000" volume="1.0"/› ‹artifact category="technology" name="Psychic Probe" stv="68333" volume="0.3"› ‹color green="The Psychic Probe is glowing green." yellow="The Psychic Probe is glowing yellow." red="The Psychic Probe is glowing red." /› ‹/artifact› ‹/technologies› ‹trade_goods› ‹stg era="Stone Age" name="Art Object" stv="290" volume="1.0"/› ‹stg era="Metal Age" name="Cloths And Tapestries" stv="360" volume="1.0"/› ‹stg era="Industrial Age" name="Chemicals" stv="415" volume="1.0"/› ‹stg era="Starfaring Age" name="Crew Rations" stv="900" volume="1.0"/› ‹specialty name="Amusoballs" stv="1000" volume="1.0"/› ‹/trade_goods› ‹lifeforms› ‹lifeform name="Angry Pygmy" stv="420" volume="2.0"/› ‹/lifeforms› ‹minerals› ‹mineral name="Endurium" stv="1000" type="contraband" volume="1.0"/› ‹mineral name="Shyneum" stv="500" type="fuel" volume="1.0"/› ‹mineral name="Antimony" stv="280" volume="1.0"/› ‹mineral name="Aluminum" stv="220" type="repair" volume="1.0"/› ‹/minerals› ‹/root›
The file "trade_messages.xml" is designed as a master list of messages in the game that are displayed to the player during the haggling process. Under the root element, entries in the XML file may be under of one of the following categorical elements: "greeting" (for messages given to the player upon entering the center), "open" (a message given to the player when the trader makes their opening bid), "counter" (a message given to the player when the trader makes a counter-offer), "refuse" (a message given to the player when the trader can budge more but decides not to), "refuse_final" (a message given to the player when the trader genuinely will not budge further), "angry_end" (a message signalling an unsuccessful trade with the trader becoming angry with the player), "accept" (a message signalling a successful transaction), "player_refuses_cont" (a message given when the player refuses an offer and the trader decides to continue trade), "player_refuses_end" (a message signalling an unsuccessful trade given when the player refuses an offer and the trader decides not to deal further), "out_of_stock" (a message signalling the trader is out of stock, which shouldn't be necessary for SF3 but will be included anyway), "thanks" (a message displayed when the player selects "Exit"), "no_sale" (a message given when the player attempts to begin selling an item to the trader that they do not want, also shouldn't be necessary for SF3), "techlock" (a message given when the player attempts to buy a piece of technology without selling a desired specialty trade good to that species first), "techunlock" (a message given to the player signalling that they may now attempt to buy technologies from the trader), "fed_up" (a message given to the player upon entering a trade center wherein they are notified that they will not be allowed to conduct any trade), "second_try", "third_try", "fourth_try", and "fifth_try" (all of which outline subsequent attempts to conduct a transaction on an item after previous unsuccessful trades of that item). Actual entries are empty sub-elements under each of these categories. There are three optional attributes that may or may not be included in an entry: "color" (either red, green, or yellow; this indicates at what "trader color" corresponding to a Psychic Probe the trader may use that particular message), "race" (a message specific to a given species; this is required if the entry's category is "greeting"), and "action" (either "buy" or "sell", this indicates the message can only be used for the specified type of transaction). All entries must contain the "msg" attribute, which contains the actual text of the message.
Messages included in the XML may contain one of four special "tags". Before any messages in the engine are displayed, the _hackAndSlash() method will scan the message for these tags and replace them with the appropriate information if they are encountered. These tags are "^" for the player's current offer, "@" for the player's previous offer, "%" for the trader's current offer, "*" for the name of the good in the current transaction, "#" for the name of the player's Captain character, and "|" for the trader's own name.
Sample structure of the trade_messages.xml file:
‹?xml version="1.0" encoding="UTF-8"?› ‹root table-name="trade messages" version="0.1"› ‹greeting race="Aeoruiiaeo" msg="Floating...drifting...we pass one another...we exchange items...out of the darkness...into the darkness...it is all the same...do not be deceived by form..." /› ‹open msg="Ah yes, the *. I can see that you are a trader of distinction. Let us say...%." /› ‹open action="sell" msg="This is twice as high as my normal price, but times are hard. Just this once I will accept %." /› ‹counter msg="Perhaps you would agree to %." /› ‹counter action="buy" msg="I am afraid I cannot go that low...I will make next to no profit, but since you seem like such a nice and personable alien, I will offer %." /› ‹refuse msg="^ is hardly more acceptable than *. % is truly as low as I can go." /› ‹refuse_final msg="I'm afraid I can't hear you. I am afraid % is my very lowest price, as I said." /› ‹angry_end msg="You insult me. I am out of patience." /› ‹accept msg="I agree to ^." /› ‹player_refuses_cont msg="Now, let us not too be hasty. Suppose I were to agree to...oh...say...%?" /› ‹player_refuses_end msg="So be it." /› ‹out_of_stock msg="We regret to say that we are now out of stock on this item." /› ‹thanks color="green" msg="Any goods purchased will be transferred to your ship. Thank you for your patronage and have a nice day." /› ‹no_sale color="yellow" msg="We are not interested in buying *. " /› ‹techlock msg="Perhaps if you were to sell me something I really want, I might consider selling the *." /› ‹techunlock msg="Thank you for selling us that; it is something we value most highly. We are in your debt." /› ‹fed_up msg="I am not interested in trading." /› ‹second_try msg="If memory serves me, we have already been through this. Perhaps you would again like to reject my more than fair price of %." /› ‹third_try msg="I see. You believe that if you keep asking I will eventually give in. It has a small flaw. It is wrong. I am out of patience." /› ‹fourth_try msg="You have rejected my prices for the * already. Do not annoy my further. I have lost patience with this exchange." /› ‹fifth_try msg="Oh, so you wish to make me an offer so that you can once again refuse it. Your sense of humor is indeed alien. I am out of patience. I see no point in continuing trade." /› ‹/root›
The file "trade_data.xml" is designed as a master list of locations in the game at which trade can be conducted; the game will assume that trade cannot take place at any location not explicitly placed upon this list. This list will also indicate any specific goods that can be bought or sold at a given location, the technological and economic level of that location, the disposition of traders there, and their skill level at negotiation. Under the root element, entries in the XML file may be one of two categorical elements: "trader" (which outlines specific information needed for trade at a given location) or "skillscores" (which outlines the Negotiate Skill Score of a trader of that race for all difficulty levels). Actual entries are empty sub-elements under one of these categories. Trader elements have the following attributes: "location" (a four element tuple which contains sector, x-coordinate, y-coordinate and orbit, in that order), "species" (the species being traded with at that location, needed for pictures), "tech" (tech level, which may restrict the sale of certain goods at some locations), "econ" (economic level, which has a bearing on the computer's selection of the best price points (BPP) for all goods), "style" (bartering style, which again has a bearing on the BPP selection), "willbuy" (a list of all goods that will absolutely be bought at that location), "willsell" (a list of all goods that will absolutely be sold at that location), "skill" (the trader's skill category, which sets their Negotiate Skill and bonus during trade), "skillbonus" (an absolute modifier to the score that would be indicated based on the "skill" attribute and player difficulty level; this can be negative if desired), and "text" (the species descriptive text, which appears in the trade buoy orbiting the world). Of these attributes, only willbuy and willsell are optional; all other attributes must be included in a given entry.
Sample structure of the trade_data.xml file:
<?xml version="1.0" encoding="UTF-8"?> <root table-name="trade data" version="0.1"> <trader location="delta,184,148,6" species="Aeoruiiaeo" tech="Metal Age" econ="Level" style="Haggle A Little" willbuy="Dreamgrids*, Data Crystals, Black Acid Squirter, Glowing Spinner, Grey Anemone"<br/> willsell="Field Stunner, Mip Fur" skill="average" skillbonus="0" text="A race of very tall, slender, hairless bipeds. They spend the greater part of thier time in a waking dream state with the use of<br/> Dreamgrids made by the Arla." /> <skillscores skilllevel="poor" level_zero="0" level_one="11" level_two="17" level_three="18" level_four="24" level_five="25" level_six="31" level_seven="32" level_eight="38" level_nine="39" level_ten="40" /> </root>
This is current as of December 22, 2010.
This module is currently in the completed design phases; specific descriptions of the intended functions of modules have been written at this point, but no actual code has been written related to the module as yet (with the possible exception of XML files). The items.xml file has been redesigned twice, and should be close to a final permutation at this point, though I'll need to test the module before I know for sure. The trade_data.xml file is still incomplete, though its basic design is pretty well set. Likewise, the trade_messages.xml file is complete enough to be usable, but there have been no new messages added specifically for SF3. Artwork will need to be designed for this module, but until the game's standards are set in stone (as far as an engine to base it off of, anyway), it'll be difficult to ascertain exactly what is needed.
CURRENT NOTES: During the brief development of the Starport Bank Office Sub-Module, a list was added to the Player object to keep track of transactions (this list is the same one that is accessed by the aforementioned Bank Module when it is activated). It was realized that in order for that list to do any good, any module that conducts financial transactions will need to add an entry to this list whenever a transaction is concluded. Such a function has not yet been written into this module; that will need to be done at some point in the future. This module will maintain completed design status until work on the necessary method's description begins.