Compare commits

..

14 Commits

32 changed files with 337 additions and 380 deletions

View File

@ -7,6 +7,8 @@ run: build
./$(NAME)
build:
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging
build_entitas_logging:
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging
debug:
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging Dentitas_debug_generator
# I did not enabled --debug because it crashes.

View File

@ -16,8 +16,8 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
## Usage
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml"
./core # run the server (high performance for data)
xdg-open https://localhost:3000 # open the game UI (browser UI for simplicity)
## Development
@ -36,7 +36,7 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
## Contributors
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer.
- [Arthur Poulet](https://git.sceptique.eu/Sceptique) - creator and maintainer
## Particular mentions

View File

@ -60,7 +60,7 @@ items:
c_plant:
<<: *template_default
title: Chemical plant
description: Series of chemical transformation machines.
description: Transformation machines that can produce almost any kind of material via chemistery, such as super-acides, rare gaz, etc. using lots of basic materials.
consumes:
mineral:
function: linear
@ -84,7 +84,7 @@ items:
l_plant:
<<: *template_default
title: Logistic assembly
description: An assembly center site that make trunks, trains, planes, and civilan space transports.
description: An assembly center site that make trunks, trains, planes, and civilan space transports. It also provide a better transportation network.
consumes:
alloy:
function: linear

View File

@ -36,7 +36,7 @@ items:
e_plant:
<<: *template_default
title: Energy plant
description: Energy grid that produce energy using nuclear isotops, sun, wind, and fossil fuel.
description: Energy grid that produce energy using nuclear isotops, sun, wind, and fossil fuel, and also can fill energy storage means with chemistery.
prods:
energy:
function: linear
@ -51,7 +51,7 @@ items:
mine:
<<: *template_default
title: Mines
description: Mines are polluting industry to generate tons of basic ressources required for most of the industry.
description: Mines are a polluting industry that can generate tons of basic ressources required for most of the industry. Network of mines generate it's own cheminals.
consumes:
energy:
function: linear
@ -62,16 +62,20 @@ items:
function: linear
coefs:
a: 10
chemical:
function: linear
coefs:
a: 0.01
wastes:
pollution:
function: linear
coefs:
a: 1
farm:
agrifood:
<<: *template_default
title: Farmed lands
description: Large portions of the land space is used to produce nutrient. It growth crops, animal farms, etc. using standard mechanized tools and vehicules.
description: Large portions of the land space is used to produce nutrient. It growth crops, animal farms, etc. using standard mechanized tools and vehicules. Food can then be distributed via a dense network of distributors.
consumes:
mineral:
function: linear

View File

@ -29,7 +29,7 @@ templates:
max:
function: linear
coefs:
b: 10_000
b: 40_000
items:
@ -51,12 +51,12 @@ items:
mineral:
function: linear
coefs:
a: 10000000
a: 10000
f_store:
<<: *template_default
title: Food storage
description: Protective centers for nutrients and food supplies
description: Protective centers for nutrients and food supplies.
consumes:
energy:
function: linear

View File

@ -1,35 +1,35 @@
energy:
name: Energy
description: Represents how many machines we can move at the same time.
description: Represents how many machines we can move at the same time. Energy unit represents both the energy immediate usage and storage by technological means.
food:
name: Food
description: Standard food with low technological ehancements
description: Standard food with low technological ehancements, along with means and services to distribute it properly.
food2:
name: Synthetic Food
description: Artificialy engineered very efficient food
description: Artificialy engineered very efficient food and distribution.
mineral:
name: Simple Minerals
name: Simple Minerals in a proper concentration.
minerals2:
name: Rare Minerals
name: Rare Minerals.
chemical:
name: Chemicals
description: Usually extracted from minerals with chemistery
description: Materials usually extracted from minerals with chemistery, with the infrastructure for safe storage and exploitation.
require: mineral
alloy:
name: Alloys
description: Standard alloys
description: Standards alloys for most industrial and military usages.
require: mineral
alloy2:
name: Super Materials
description: Super alloys with near-magical properties
description: Super alloys with near-magical properties compared to modern age.
require: mineral
weapon:
name: Weaponery
description: Bombs, guns, bullets, armors, utilitary, tanks, fighters
description: Bombs, guns, bullets, armors, utilitary, tanks, fighters, artillery, all the things that are small enough to fit in a carrier.
require: alloy
logistic:
name: Logistic
description: Civilan ships for fret and travellers
description: Civilan ships and means of transportations for high volume fret and travellers.
pollution:
name: Pollution
description: Represent the wastes of industrial activities, that is stored on the planet more or less randomly.
description: Represent the wastes of industrial activities, that is stored on the planet more or less efficiently and have a negative impact on productivity and life.

View File

@ -36,7 +36,7 @@ Lalande
Lampadas
Lankiveil
Lernaeus
Luyten's Star
Luyten
Mu Arae
Muritan
Naraj
@ -53,11 +53,11 @@ Selusa
Sikun
Sun
Synchrony
Trappist
Tau Ceti
Tauri
Tegeuse
Tleilax
Trappist
Tupile
Upsilon Andromedae
Ursae Majoris
@ -65,4 +65,3 @@ Virginis
Wasp
Xo
Yz Ceti
c

View File

@ -1,5 +0,0 @@
type Query {
empires(name: String!): String!
day(): Int!
}

View File

@ -1,28 +1,20 @@
version: 2.0
shards:
backtracer:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.1
crsfml:
git: https://github.com/oprypin/crsfml.git
version: 2.5.2
entitas:
git: https://github.com/spoved/entitas.cr.git
version: 1.4.5
exception_page:
git: https://github.com/crystal-loot/exception_page.git
version: 0.2.2
imgui:
git: https://github.com/oprypin/crystal-imgui.git
version: 1.87
graphql:
git: https://github.com/graphql-crystal/graphql.git
version: 0.4.0
kemal:
git: https://github.com/kemalcr/kemal.git
version: 1.2.0
radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1
imgui-sfml:
git: https://github.com/oprypin/crystal-imgui-sfml.git
version: 1.87
spoved:
git: https://github.com/spoved/spoved.cr.git

View File

@ -15,7 +15,9 @@ license: GPLv3
dependencies:
entitas:
github: spoved/entitas.cr
graphql:
github: graphql-crystal/graphql
kemal:
github: kemalcr/kemal
crsfml:
github: oprypin/crsfml
imgui:
github: oprypin/crystal-imgui
imgui-sfml:
github: oprypin/crystal-imgui-sfml

View File

@ -1,4 +0,0 @@
module TETU::API
end
require "./api/*"

View File

@ -1,21 +0,0 @@
require "./objects"
module TETU::API::Definitions
@[GraphQL::Object]
class GameQuery < GraphQL::BaseQuery
@[GraphQL::Field]
def hello(name : String) : String
"Hello, #{name}!"
end
@[GraphQL::Field]
def empire : Empire
Empire.new
end
@[GraphQL::Field]
def planet(id : ID? = nil) : Planet
Planet.new(id: id)
end
end
end

View File

@ -1,50 +0,0 @@
class TETU::API::HttpServer
def self.start
spawn do
Kemal.run do |config|
server = config.server.not_nil!
server.bind_tcp(
host: ENV.fetch("HOST", "127.0.0.1"),
port: ENV.fetch("PORT", "3000").to_i,
)
end
end
end
end
require "./game_query"
schema = GraphQL::Schema.new(TETU::API::Definitions::GameQuery.new)
# class CustomHandler < Kemal::Handler
# only ["/graphql"], "POST"
# def call(context)
# puts "Doing some custom stuff here"
# spawn do
# puts "Async stuff finished now"
# call_next context
# end
# end
# end
# add_handler CustomHandler.new
module TETU::API
PLAYER_CHANNEL = Channel(Tuple(String, Channel(String))).new
RESPONSE_CHANNEL = Channel(String).new
end
post "/graphql" do |env|
env.response.content_type = "application/json"
query = env.params.json["query"].as(String)
variables = env.params.json["variables"]?.as(Hash(String, JSON::Any)?)
operation_name = env.params.json["operationName"]?.as(String?)
context = TETU::API::Definitions::PlayerContext.new(TETU::API::PLAYER_CHANNEL)
schema.execute(query, variables, operation_name, context)
end
get "/" do |env|
"<html></html>"
end

View File

@ -1,3 +0,0 @@
require "./objects/base"
require "./objects/planet"
require "./objects/all"

View File

@ -1,48 +0,0 @@
require "./base"
require "./planet"
module TETU::API::Definitions
@[GraphQL::Object]
class Star < GraphQL::BaseObject
@@i : ID = 0
def initialize(id : Int32? = nil)
if id.nil?
@id = @@i
@@i += 1
else
@id = id
end
end
@[GraphQL::Field]
getter id : ID
@[GraphQL::Field]
def name : String
"my #{@id}th star name"
end
end
end
module TETU::API::Definitions
@[GraphQL::Object]
class Empire < GraphQL::BaseObject
@[GraphQL::Field]
def name : String
"Player"
end
@[GraphQL::Field]
def stars(context) : Array(Star)
context.player_channel.send({ "stars", TETU::API::RESPONSE_CHANNEL })
answer = TETU::API::RESPONSE_CHANNEL.receive
answer.split(",").map { |name| Star.new }
end
@[GraphQL::Field]
def planets : Array(Planet)
[] of Planet
end
end
end

View File

@ -1,8 +0,0 @@
module TETU::API::Definitions
alias ID = Int32
class PlayerContext < GraphQL::Context
def initialize(@player_channel : Channel(Tuple(String, Channel(String))))
end
end
end

View File

@ -1,23 +0,0 @@
module TETU::API::Definitions
@[GraphQL::Object]
class Planet < GraphQL::BaseObject
@@i : ID = 0
def initialize(id : Int32? = nil)
if id.nil?
@id = @@i
@@i += 1
else
@id = id
end
end
@[GraphQL::Field]
getter id : ID
@[GraphQL::Field]
def name : String
"my #{@id}th planet name"
end
end
end

View File

@ -18,79 +18,6 @@ class TETU::Named < Entitas::Component
end
require "./game/resources"
# TODO: why is it not a component ????? should fix that
class TETU::InfrastructureUpgrade
spoved_logger level: :info, io: STDOUT, bind: true
alias Costs = Hash(Resources::Name, Float64)
property id : String
property costs_by_tick : Costs
property costs_start : Costs
property end_tick : TETU::Tick
property current_tick : TETU::Tick
@finished = false
def to_s
"#{@id} (#{@current_tick}/#{@end_tick})"
end
def finished?
@finished
end
def finish!
@finished = true
end
def initialize(@id, @costs_by_tick, @costs_start, @end_tick, @current_tick = 0i64)
end
def self.from_infrastructure(id : String, tier : Int32)
# TODO: must read the properties of the blueprints to define the costs
free_instant(id)
end
def self.free_instant(id : String)
new(
id: id,
costs_by_tick: Costs.new,
costs_start: Costs.new,
end_tick: 0i64,
current_tick: 0i64,
)
end
spoved_logger level: :info, io: STDOUT, bind: true
def self.from_blueprint(infra_id : String, tier : Number)
blueprint = Helpers::InfrastructuresFileLoader.all[infra_id]
total_costs = blueprint.build.costs.transform_values { |f| f.execute(tier) }
upfront_costs = total_costs.transform_values { |v| v * blueprint.build.upfront }
duration = blueprint.build.duration.execute(tier)
tick_costs = total_costs.transform_values { |v| v * (1.0 - blueprint.build.upfront) / duration }
logger.debug { "" }
logger.debug { "> Create from blueprint" }
upgrade = new(
id: infra_id,
costs_by_tick: tick_costs,
costs_start: upfront_costs,
end_tick: duration.to_i64,
current_tick: 0i64,
)
logger.debug { {blueprint: blueprint} }
logger.debug { {upgrade: upgrade} }
logger.debug { "" }
upgrade
end
end
@[Context(Game)]
class TETU::InfrastructureUpgrades < Entitas::Component
prop :upgrades, Array(InfrastructureUpgrade), default: Array(InfrastructureUpgrade).new
def to_s
"InfrastructureUpgrades: #{upgrades.map(&.to_s)}"
end
end
require "./game/infrastructure"
require "./game/*"

View File

@ -0,0 +1,73 @@
# TODO: why is it not a component ????? should fix that
class TETU::InfrastructureUpgrade
spoved_logger level: :info, io: STDOUT, bind: true
alias Costs = Hash(Resources::Name, Float64)
property id : String
property costs_by_tick : Costs
property costs_start : Costs
property end_tick : TETU::Tick
property current_tick : TETU::Tick
@finished = false
def to_s
"#{@id} (#{@current_tick}/#{@end_tick})"
end
def finished?
@finished
end
def finish!
@finished = true
end
def initialize(@id, @costs_by_tick, @costs_start, @end_tick, @current_tick = 0i64)
end
def self.from_infrastructure(id : String, tier : Int32)
# TODO: must read the properties of the blueprints to define the costs
free_instant(id)
end
def self.free_instant(id : String)
new(
id: id,
costs_by_tick: Costs.new,
costs_start: Costs.new,
end_tick: 0i64,
current_tick: 0i64,
)
end
spoved_logger level: :info, io: STDOUT, bind: true
def self.from_blueprint(infra_id : String, tier : Number)
blueprint = Helpers::InfrastructuresFileLoader.all[infra_id]
total_costs = blueprint.build.costs.transform_values { |f| f.execute(tier) }
upfront_costs = total_costs.transform_values { |v| v * blueprint.build.upfront }
duration = blueprint.build.duration.execute(tier)
tick_costs = total_costs.transform_values { |v| v * (1.0 - blueprint.build.upfront) / duration }
logger.debug { "" }
logger.debug { "> Create from blueprint" }
upgrade = new(
id: infra_id,
costs_by_tick: tick_costs,
costs_start: upfront_costs,
end_tick: duration.to_i64,
current_tick: 0i64,
)
logger.debug { {blueprint: blueprint} }
logger.debug { {upgrade: upgrade} }
logger.debug { "" }
upgrade
end
end
@[Context(Game)]
class TETU::InfrastructureUpgrades < Entitas::Component
prop :upgrades, Array(InfrastructureUpgrade), default: Array(InfrastructureUpgrade).new
def to_s
"InfrastructureUpgrades: #{upgrades.map(&.to_s)}"
end
end

View File

@ -1,15 +1,19 @@
@[Context(Game)]
class TETU::Population < Entitas::Component
alias Food = Hash(String, Float64)
DEFAULT_FOOD = { "food" => 1.0/100.0.millions }
prop :amount, Float64, default: 0.0
prop :foods, Food, default: DEFAULT_FOOD
MIN_RANDOM_POP = 10_000.0
MAX_RANDOM_POP = 10_000_000_000.0
MIN_RANDOM_POP = 10.0.millions
MAX_RANDOM_POP = 10.0.billions
def self.generate(entity)
def self.generate_for(entity)
entity.add_population amount: (MIN_RANDOM_POP..MAX_RANDOM_POP).sample.round
end
def to_s
Helpers::Numbers.humanize(number: @amount, round: 2)
def to_s(round : Int32 = 2)
Helpers::Numbers.humanize(number: @amount, round: round)
end
end

View File

@ -27,6 +27,10 @@ class TETU::Resources < Entitas::Component
def humanize(sep = "\n")
map { |k, store| "#{k}: #{store.humanize}" }.join(sep)
end
def amount_hash
transform_values { |store| store.amount }
end
end
# alias Prod = Tuple(Name, Float64)
@ -93,7 +97,7 @@ class TETU::Resources < Entitas::Component
def self.default
stores = Stores.new
stores["pollution"] = Store.new(amount: 0.0, max: 1_000_000.0)
stores["pollution"] = Store.new(amount: 0.0, max: 1.0.millions)
infras = Infras.new

View File

@ -1,20 +1,24 @@
# require "log"
require "yaml"
require "entitas"
require "graphql"
require "crsfml"
require "imgui"
require "imgui-sfml"
require "spoved/logger"
require "kemal"
module TETU
spoved_logger level: :info, io: STDOUT, bind: true
# TODO: TO BE USED
# TO BE USED
module Systems
# Log = TETU::Log.for(self)
spoved_logger level: :info, io: STDOUT, bind: true
end
# TODO: TO BE USED
# TO BE USED
module Components
# Log = TETU::Log.for(self)
spoved_logger level: :info, io: STDOUT, bind: true
end
@ -24,9 +28,9 @@ end
require "./helpers/*"
require "./core/*"
require "./ui_service"
require "./components"
require "./systems"
require "./api"
class TETU::EconomicSystems < Entitas::Feature
def initialize(contexts : Contexts)
@ -38,16 +42,22 @@ class TETU::EconomicSystems < Entitas::Feature
end
end
class TETU::PlayerSystems < Entitas::Feature
class TETU::UiSystems < Entitas::Feature
def initialize(contexts : Contexts)
@name = "Player Systems"
add RequestHandlerSystem.new(contexts, player_channel)
@name = "UI Systems"
add UiInitSystem.new(contexts)
add UiBackgroundSystem.new(contexts)
add UiEmpireSystem.new(contexts)
add UiPlanetSystem.new(contexts)
add UiDrawSystem.new(contexts) # keep at the end
end
end
class TETU::TimeSystems < Entitas::Feature
def initialize(contexts : Contexts)
@name = "Time Systems"
add TimeSystem.new(contexts)
end
end
@ -58,12 +68,12 @@ class TETU::MainWorld
def start
# get a reference to the contexts
contexts = Contexts.shared_instance
# create the systems by creating individual features
@systems = Entitas::Feature.new("systems")
.add(TimeSystems.new(contexts))
.add(EconomicSystems.new(contexts))
.add(PlayerSystems.new(contexts))
# .add(UiSystems.new(contexts))
.add(UiSystems.new(contexts))
@systems.init
end
@ -71,6 +81,7 @@ class TETU::MainWorld
# call execute on all the ExecuteSystems and
# ReactiveSystems that were triggered last frame
@systems.execute
# call cleanup on all the CleanupSystems
@systems.cleanup
end
@ -104,11 +115,9 @@ module TETU
t2 = Time.local
logger.debug { "Duration: #{t2 - t1}" }
logger.debug { "" }
sleep 0.1
end
end
end
end
TETU::API::HttpServer.start
TETU.main_loop

View File

@ -1,33 +1,33 @@
require "./configuration"
# class TETU::Window
# @@instance = Window.new
class TETU::Window
@@instance = Window.new
# def self.instance
# @@instance
# end
def self.instance
@@instance
end
# GALAXY_WIDTH = TETU::MAX_X
# GALAXY_HEIGHT = TETU::MAX_Y
# UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
# UI_HEIGHT = GALAXY_HEIGHT
# SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64
GALAXY_WIDTH = TETU::MAX_X
GALAXY_HEIGHT = TETU::MAX_Y
UI_WIDTH = GALAXY_WIDTH + TETU::UI_CONF["right_sidebar"].as_i64
UI_HEIGHT = GALAXY_HEIGHT
SQUARE_SIZE = TETU::UI_CONF["square_size"].as_i64
# GALAXY = SF::Texture.from_file("assets/#{GALAXY_WIDTH}x#{GALAXY_HEIGHT}/galaxy.jpg")
GALAXY = SF::Texture.from_file("assets/#{GALAXY_WIDTH}x#{GALAXY_HEIGHT}/galaxy.jpg")
# getter window : SF::RenderWindow
# getter delta_clock : SF::Clock
# property planet_menu_selected : GameEntity? = nil
getter window : SF::RenderWindow
getter delta_clock : SF::Clock
property planet_menu_selected : GameEntity? = nil
# def initialize
# @window = SF::RenderWindow.new(
# SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
# "To the End of The Universe",
# )
# @delta_clock = SF::Clock.new
# end
def initialize
@window = SF::RenderWindow.new(
SF::VideoMode.new(UI_WIDTH, UI_HEIGHT),
"To the End of The Universe",
)
@delta_clock = SF::Clock.new
end
# def [](k)
# @data[k]
# end
# end
def [](k)
@data[k]
end
end

View File

@ -15,3 +15,17 @@ module TETU::Helpers::Numbers
end
end
end
struct Number
def billions
self * TETU::Helpers::Numbers::BILLION
end
def millions
self * TETU::Helpers::Numbers::MILLION
end
def thousands
self * TETU::Helpers::Numbers::THOUSAND
end
end

23
src/helpers/ratio.cr Normal file
View File

@ -0,0 +1,23 @@
module Ratio
alias IdValue = Hash(String, Number)
# minimum ratio of right/left, or nil if missing keys in right
def self.minimum_reverse(left : IdValue, right : IdValue)
return nil if left.keys & right.keys != left.keys
left.map do |id, left_value|
right_value = right[id]
right_value / left_value
end.min
end
# minimum ratio of left/right, or nil if missing keys in right
def self.minimum(left : IdValue, right : IdValue)
return nil if left.keys & right.keys != left.keys
left.map do |id, left_value|
right_value = right[id]
left_value / right_value
end.min
end
end

View File

@ -1,2 +1,2 @@
require "./systems/*"
require "./systems/player/*"
require "./systems/ui/*"

View File

@ -1,7 +1,7 @@
require "../components"
class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
spoved_logger level: :info, io: STDOUT, bind: true
spoved_logger level: :debug, io: STDOUT, bind: true
def initialize(@contexts : Contexts)
@time_context = @contexts.time
@ -21,9 +21,14 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
producer_group = @contexts.game.get_group Entitas::Matcher.all_of(Resources, Population, ManpowerAllocation)
producer_group.entities.each do |e|
next if !e.resources.can_produce?
if !e.resources.can_produce?
logger.debug { "#{e.named} cannot produces" }
next
end
logger.debug { "#{e.named} produces now" }
e.resources.infras.each do |infra_id, infra|
logger.debug { "#{e.named.to_s} produces now via #{infra.id}" }
rate = prod_rate(infra, e)
prod_rates = infra.prods.map { |res, prod| apply_prod(infra: infra, res: res, rate: rate, prod: prod) }
real_prod_rate = prod_rates.empty? ? rate : prod_rates.max
@ -42,8 +47,16 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
end
def prod_rate(infra : Resources::Infra, producer : GameEntity) : Float64
return 1.0 if infra.consumes.empty?
return 0.0 if infra.consumes.any? { |res, _value| infra.stores[res]?.nil? }
if infra.consumes.empty?
logger.debug { "no consumption, rate 1.0" }
return 1.0
end
if infra.consumes.any? { |res, _value| infra.stores[res]?.nil? }
logger.debug { "missing consumable, 0.0" }
return 0.0
end
# TODO: another function for pop.amount < manpower.optimal
allocated_manpower = producer.manpower_allocation.absolute[infra.id]
maximal_rate =
@ -54,14 +67,15 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
end
limited_rate = (infra.consumes.map { |res, value| infra.stores[res].amount / value } + [maximal_rate]).min
# if infra.id == "mine" || true
# logger.debug { "producer.named.name=#{producer.named.name}" }
# logger.debug { "infra.id=#{infra.id}" }
# logger.debug { "allocated_manpower=#{allocated_manpower}" }
# logger.debug { "infra.manpower.optimal=#{infra.manpower.optimal}" }
# logger.debug { "infra.manpower.min=#{infra.manpower.min} " }
# logger.debug { "maximal_rate=#{maximal_rate} " }
# logger.debug { "limited_rate=#{limited_rate}" }
# logger.debug { "" }
logger.debug { "" }
logger.debug { "producer.named.name=#{producer.named.name}" }
logger.debug { "infra.id=#{infra.id}" }
logger.debug { "allocated_manpower=#{allocated_manpower}" }
logger.debug { "infra.manpower.optimal=#{infra.manpower.optimal}" }
logger.debug { "infra.manpower.min=#{infra.manpower.min} " }
logger.debug { "maximal_rate=#{maximal_rate} " }
logger.debug { "limited_rate=#{limited_rate}" }
logger.debug { "" }
# end
limited_rate
end
@ -69,10 +83,10 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
# returns the real production rate, limited by the storage
# @param rate : the maximum production we should use
def apply_prod(infra : Resources::Infra, rate : Float64, res : Resources::Name, prod : Float64) : Float64
# logger.debug { "apply_prod wants rate=#{rate} res=#{res} prod=#{prod}" }
logger.debug { "apply_prod wants rate=#{rate} res=#{res} prod=#{prod}" }
store = infra.stores[res]?
if store.nil? || rate > 0 && store.amount == store.max
# logger.debug { "apply_prod applied rate=0.0 res=#{res} prod=#{prod}" }
logger.debug { "max storage: apply_prod applied rate=0.0 res=#{res} prod=#{prod}" }
return 0.0
end
@ -88,7 +102,7 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
store.amount = new_amount
# logger.debug { "apply_prod applied rate=#{rate} res=#{res} prod=#{prod}" }
logger.debug { "ok: apply_prod applied rate=#{rate} res=#{res} prod=#{prod}, store=#{new_amount}" }
return rate
end

View File

@ -1,6 +1,6 @@
class TETU::GalaxyInitializerSystem
include Entitas::Systems::InitializeSystem
spoved_logger level: :info, io: STDOUT, bind: true
spoved_logger level: :debug, io: STDOUT, bind: true
def initialize(@contexts : Contexts); end
@ -9,6 +9,7 @@ class TETU::GalaxyInitializerSystem
EMPIRE_AMOUNT = AI_AMOUNT + 1 # add the player
AI_MIN_PLANETS = GALAXY_CONF["ai_start_populated_bodies_amount"].as_i
PLANET_POP_PROBA = TETU::GALAXY_CONF["populated_planets_proba"].as_f
AI_DEBUG_0_IS_PLANET = GALAXY_CONF["ai_debug_0_is_planet"].as_bool?
# NO_SPACE_EMPIRE_ID = 100001
def init
@ -44,9 +45,11 @@ class TETU::GalaxyInitializerSystem
bodies_amount = Helpers::Planet::BODIES_STATISTICS.sample
bodies_amount = AI_MIN_PLANETS if !empire_id.nil? && bodies_amount < AI_MIN_PLANETS
logger.debug { "generate bodies_amount=#{bodies_amount}" }
bodies_amount.times.map do |index|
body_type = Helpers::Planet::TYPES_STATISTICS.sample
body_type = :planet if AI_DEBUG_0_IS_PLANET && index == 0
ids_trash[body_type] += 1
body = generate_body(star: star, index: index, body_type: body_type, ids_trash: ids_trash)
if body_type == :asteroid_belt
@ -104,20 +107,33 @@ class TETU::GalaxyInitializerSystem
body
end
DEFAULT_INFRASTRUCTURES = %w[e_store m_store f_store e_plant mine farm a_store l_store a_plant l_plant]
DEFAULT_INFRASTRUCTURES = {
"e_store" => 2,
"m_store" => 2,
"f_store" => 5,
"e_plant" => 2,
"mine" => 2,
"agrifood" => 5,
"a_store" => 1,
"l_store" => 1,
"a_plant" => 1,
"l_plant" => 1,
}
def populate(body)
# logger.debug { "populate: #{body.named.name}..." }
pop_amount = ((10_000.0)..(10_000_000_000.0)).sample
body.add_population amount: pop_amount
pop_amount = ((10_000.0)..(10.0.billions)).sample
Population.generate_for(body)
body.replace_component(Resources.default_populated)
body.add_infrastructure_upgrades
body.add_manpower_allocation
body.manpower_allocation.available = body.population.amount
DEFAULT_INFRASTRUCTURES.each do |infra_id|
DEFAULT_INFRASTRUCTURES.each do |infra_id, infra_level|
infra_level.times do
upgrade = InfrastructureUpgrade.free_instant(id: infra_id)
body.infrastructure_upgrades.upgrades << upgrade
end
end
# logger.debug { "populated: #{body.named.name}, now #{body.resources.to_s}, with #{body.infrastructure_upgrades.upgrades.size} upgrade to do..." }
body
end

View File

@ -81,14 +81,14 @@ class TETU::InfrastructureUpgradesSystem < Entitas::ReactiveSystem
end
def pay_upgrade_tick(resources : Resources, upgrade : InfrastructureUpgrade, costs : InfrastructureUpgrade::Costs)
if costs.all? { |res, amount| resources.stores[res].amount >= amount }
if !(missing_resource = costs.find { |res, amount| resources.stores[res].amount < amount })
# pay the upgrade with local store
costs.all? { |res, amount| resources.stores[res].amount -= amount }
upgrade.current_tick += 1
logger.debug { "paid tick upgrade" }
else
# if we can't pay the upgrade, we will "loose" one tick due to maintenance
logger.debug { "cannot pay upgrade" }
logger.debug { "cannot pay upgrade because #{missing_resource}" }
upgrade.end_tick += 1
end

View File

@ -1,20 +0,0 @@
class TETU::RequestHandlerSystem
include Entitas::Systems::ExecuteSystem
spoved_logger level: :info, io: STDOUT, bind: true
getter player_channel : Channel(Tuple(String, Channel(String)))
@contexts : Contexts
def initialize(@contexts, @player_channel)
end
def execute
message, responder = player_channel.receive
if message == "stars"
stars = @contexts.game.get_group Entitas::Matcher.all_of(Named, Position, CelestialBody, PlayerOwned).none_of(StellarPosition)
responder.send stars.entities.map { |entity| entity.named.name }.join(",")
else
responder.send ""
end
end
end

View File

@ -1,7 +1,7 @@
require "../components"
class TETU::PopulationGrowthSystem < Entitas::ReactiveSystem
spoved_logger level: :info, io: STDOUT, bind: true
spoved_logger level: :debug, io: STDOUT, bind: true
def initialize(@contexts : Contexts)
@time_context = @contexts.time
@ -13,15 +13,62 @@ class TETU::PopulationGrowthSystem < Entitas::ReactiveSystem
end
def execute(time_entities : Array(Entitas::IEntity))
populateds = @contexts.game.get_group Entitas::Matcher.all_of(Population)
populateds = @contexts.game.get_group Entitas::Matcher.all_of(Population, Resources)
populateds.entities.each do |e|
pop_amount = e.population.amount
foods = e.population.foods
pop_foods_needs = foods.transform_values do |food_amount_per_pop|
food_amount_per_pop * pop_amount
end
logger.debug { "pop_foods_needs=#{pop_foods_needs}" }
logger.debug { "resouces=#{e.resources.stores}" }
minimum_food_ratio = Ratio.minimum_reverse(pop_foods_needs, e.resources.stores.amount_hash) || 0.0
logger.debug { "minimum_food_ratio=#{minimum_food_ratio}" }
total_food_modifier = food_modifier(minimum_food_ratio)
logger.debug { "total_food_modifier=#{total_food_modifier}" }
new_pop_amount = pop_amount + pop_amount * (reproduction_rate(total_food_modifier))
logger.debug { "population growth: #{pop_amount} * #{new_pop_amount / pop_amount} => #{new_pop_amount}" }
e.replace_population(amount: new_pop_amount, foods: foods)
pop_foods_needs.each do |food_id, food_need|
# only consumes the need, only "theorical availability" counts, like a richness factor
food_consumption = minimum_food_ratio < 1.0 ? food_need * minimum_food_ratio : food_need
logger.debug { "population food consumption: #{food_id}:#{food_need} -> consumes #{food_consumption}" }
e.resources.stores[food_id].amount -= food_consumption
end if minimum_food_ratio > 0.0
end
end
# we also add a modifier based on food availability
# that can be between -5 for famine with 0 food
# +0.0 if food required is reach no less no more
# up to 1.0 for post-scarcity inifity food (5x food required = +0.8)
def food_modifier(available_food_ratio : Float64)
return -5.0 if available_food_ratio <= 0.0
modifier = 1.0 / -available_food_ratio + 1.0
if modifier < -5.0
-5.0 # not worse than -5
else
modifier
end
end
# let's say every adult make 1.5 child average in its average lifespan (80 years)
# and one tick is one day
reproduction_rate = 1.5 * (1.0/80.0) * (1.0/365)
new_pop_amount = pop_amount + pop_amount * reproduction_rate
# logger.debug { "population growth: {reproduction_rate:#{reproduction_rate}} {population:#{e.population.to_s}} {bonus:#{pop_amount * reproduction_rate}}" }
e.replace_population(amount: new_pop_amount)
end
# and one tick is one day (3 children per couple).
def reproduction_rate(food_modifier) : Float64
(children_per_pop_average + food_modifier) * (1.0/pop_lifespan_average) * (1.0/365.0)
end
def pop_per_food_per_tick : Float64
100.0.millions
end
def children_per_pop_average : Float64
1.5
end
def pop_lifespan_average : Float64
80.0
end
end

View File

@ -8,14 +8,23 @@ class TETU::UiService::PlanetInfrastructure < TETU::UiService
def draw
if ImGui.tree_node_ex("resources panel", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
ImGui.text @planet.named.name
draw_population
draw_resources
ImGui.tree_pop
end
end
private def draw_resources
ImGui.text @planet.named.name
private def draw_population
ImGui.text "Population:" + if @planet.has_population?
@planet.population.to_s(round: 4)
else
"None"
end
end
private def draw_resources
if @planet.has_resources?
draw_storage
ImGui.text ""