Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A smarter ShipMarket in SpaceStation #3243

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 71 additions & 24 deletions data/libs/SpaceStation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -204,40 +204,77 @@ local isPlayerShip = function (def) return def.tag == "SHIP" and def.basePrice >
local groundShips = utils.build_array(utils.filter(function (k,def) return isPlayerShip(def) and def.equipSlotCapacity.atmo_shield > 0 end, pairs(ShipDef)))
local spaceShips = utils.build_array(utils.filter(function (k,def) return isPlayerShip(def) end, pairs(ShipDef)))


-- Dynamics of ship adverts in ShipMarket --
--------------------------------------------
-- N(t) = Number of ads, lambda = decay constant:
-- d/dt N(t) = prod - lambda * N
-- and equilibrium:
-- dN/dt = 0 = prod - lambda * N_equil
-- and solution (if prod=0), with N_0 initial number:
-- N(t) = N_0 * exp(-lambda * t)
-- with tau = half life, i.e. N(tau) = 0.5*N_0 we get:
-- 0.5*N_0 = N_0*exp(-lambda * tau)
-- else, with production:
-- N(t) = prod/lambda - N_0*exp(-lambda * t)
-- We want to have N_0 = N_equil, since ShipMarket should spawn in equilibrium

-- Average number of ship for sale for station
local function N_equilibrium(station)
local pop = station.path:GetSystemBody().parent.population -- E.g. Earth=7, Mars=0.3
local pop_bonus = 9 * math.log(pop*0.45 + 1) -- something that gives resonable result
if station.type == "STARPORT_SURFACE" then
pop_bonus = pop_bonus * 1.5
end

return 2 + pop_bonus
end

-- add num random ships for sale to station ShipMarket
local function addRandomShipAdvert(station, num)
for i=1,num do
local avail = station.type == "STARPORT_SURFACE" and groundShips or spaceShips
local def = avail[Engine.rand:Integer(1,#avail)]
local model = Engine.GetModel(def.modelName)
local pattern = model.numPatterns ~= 0 and Engine.rand:Integer(1,model.numPatterns) or nil
addShipOnSale(station, {
def = def,
skin = ModelSkin.New():SetRandomColors(Engine.rand):SetDecal(def.manufacturer),
pattern = pattern,
label = Ship.MakeRandomLabel(),
})
end
end

local function createShipMarket (station)
shipsOnSale[station] = {}

local shipAdsToSpawn = Engine.rand:Poisson(N_equilibrium(station))
addRandomShipAdvert(station, shipAdsToSpawn)
end

local function updateShipsOnSale (station)
if not shipsOnSale[station] then shipsOnSale[station] = {} end
local shipsOnSale = shipsOnSale[station]

local toAdd, toRemove = 0, 0
if #shipsOnSale == 0 then
toAdd = Engine.rand:Integer(20)
elseif Engine.rand:Integer(1) > 0 then
toAdd = 1
elseif #shipsOnSale > 0 then
toRemove = 1
else
return
end
local tau = 7*24 -- half life of a ship advert in hours
local lambda = 0.693147 / tau -- removal probability= ln(2) / tau
local prod = N_equilibrium(station) * lambda -- creation probability

if toAdd > 0 then
local avail = station.type == "STARPORT_SURFACE" and groundShips or spaceShips
for i=1,toAdd do
local def = avail[Engine.rand:Integer(1,#avail)]
local model = Engine.GetModel(def.modelName)
local pattern = model.numPatterns ~= 0 and Engine.rand:Integer(1,model.numPatterns) or nil
addShipOnSale(station, {
def = def,
skin = ModelSkin.New():SetRandomColors(Engine.rand):SetDecal(def.manufacturer),
pattern = pattern,
label = Ship.MakeRandomLabel(),
})
-- remove with decay rate lambda. Call ONCE/hour for each ship advert in station
for ref,ad in pairs(shipsOnSale) do
if Engine.rand:Number(0,1) < lambda then -- remove one random ship (sold)
removeShipOnSale(station, Engine.rand:Integer(1,#shipsOnSale))
end
end

if toRemove > 0 then
removeShipOnSale(station, Engine.rand:Integer(1,#shipsOnSale))
-- spawn a new ship adverts, call for each station
if Engine.rand:Number(0,1) <= prod then
addRandomShipAdvert(station, 1)
end

if prod > 1 then print("Warning: ShipMarket not in equilibrium") end

Event.Queue("onShipMarketUpdate", station, shipsOnSale)
end

Expand Down Expand Up @@ -473,6 +510,14 @@ local function updateSystem ()
updateAdverts(stations[i])
end
end

local function createSystem()
local stations = Space.GetBodies(function (b) return b.superType == "STARPORT" end)
for i=1,#stations do
createShipMarket(stations[i])
end
end

local function destroySystem ()
equipmentStock = {}
equipmentPrice = {}
Expand Down Expand Up @@ -511,11 +556,13 @@ Event.Register("onGameStart", function ()
loaded_data = nil
end

createSystem()
updateSystem()
Timer:CallEvery(3600, updateSystem)
end)
Event.Register("onEnterSystem", function (ship)
if ship ~= Game.player then return end
createSystem()
updateSystem()
end)

Expand Down