Compare commits
14 Commits
graphql-ui
...
master
Author | SHA1 | Date | |
---|---|---|---|
915e33d183 | |||
fe5c6712d2 | |||
e9cac30467 | |||
85d73a3947 | |||
43310ecf9e | |||
beaa6ca277 | |||
8e28f3f7d7 | |||
791a24e5b3 | |||
532aae0907 | |||
dbb230123e | |||
41fa326418 | |||
b53a0e6050 | |||
ee5e3cdd3f | |||
1f4bcdae21 |
10
.drone.yml
Normal file
10
.drone.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
kind: pipeline
|
||||
name: default
|
||||
steps:
|
||||
- name: test
|
||||
image: crystallang/crystal:latest-alpine
|
||||
environment:
|
||||
commands:
|
||||
- make deps
|
||||
- make build
|
||||
- make test
|
2
Makefile
2
Makefile
|
@ -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.
|
||||
|
|
22
README.md
22
README.md
|
@ -1,6 +1,6 @@
|
|||
# TETU Core
|
||||
|
||||
A strategy & simulation game in space, inspired by Stellaris PDX
|
||||
A strategy & simulation game in space, inspired by Stellaris PDX.
|
||||
|
||||
[![Build Status](https://drone.sceptique.eu/api/badges/TETU/Core/status.svg)](https://drone.sceptique.eu/TETU/Core)
|
||||
|
||||
|
@ -10,25 +10,21 @@ Install `git`, `sfml`, `crystal`, `make`, `imgui` (`imgui-sfml` with archlinux).
|
|||
|
||||
# install dependencies first
|
||||
make deps
|
||||
|
||||
# make with imgui static linking (I think, I don't remember)
|
||||
export LD_LIBRARY_PATH="$(pwd)/cimgui"
|
||||
make release
|
||||
|
||||
# make an optimised build so it's fasteeer
|
||||
make release
|
||||
|
||||
## Usage
|
||||
|
||||
# run the
|
||||
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml"
|
||||
./core
|
||||
|
||||
# there is also a make rule that handle the libraries
|
||||
make run
|
||||
./core # run the server (high performance for data)
|
||||
|
||||
## Development
|
||||
|
||||
* See the wiki <https://git.sceptique.eu/TETU/Core/wiki>.
|
||||
* See the current kanban: <https://git.sceptique.eu/TETU/Core/projects>
|
||||
* Come talk on IRC **irc://irc.sceptique.eu#TETU**
|
||||
* See the wiki for documentation: <https://git.sceptique.eu/TETU/Core/wiki>.
|
||||
* See the current kanban for current WIP: <https://git.sceptique.eu/TETU/Core/projects>.
|
||||
* Come talk on IRC: **irc://irc.sceptique.eu:6697#TETU**.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
19
adr/0001-record-architecture-decisions.md
Normal file
19
adr/0001-record-architecture-decisions.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# 1. Record architecture decisions
|
||||
|
||||
Date: 2022-08-07
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
We need to record the architectural decisions made on this project.
|
||||
|
||||
## Decision
|
||||
|
||||
We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
|
||||
|
||||
## Consequences
|
||||
|
||||
See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
|
23
adr/0002-use-a-ecs.md
Normal file
23
adr/0002-use-a-ecs.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# 2. Use a ECS
|
||||
|
||||
Date: 2022-08-07
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
One of the target of the game is to be able to run 10.000 planets galaxy on a standard computer. This may require a very careful approch of the architecture. Naive implementation will very likely ends up with non-scalable galaxy size and limited planet amount.
|
||||
|
||||
## Decision
|
||||
|
||||
An ECS pattern will be used to handle most of the code. In particular the economic system that handle the planets.
|
||||
|
||||
The ECS is entitas.cr (see the shards.yml file).
|
||||
|
||||
## Consequences
|
||||
|
||||
Developer(s) need to know this unusual pattern.
|
||||
Performance are expected very high and scalable with threads.
|
||||
The architecture of most of the code will be defined and limited by the ECS pattern.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe Core do
|
||||
# TODO: Write tests
|
||||
|
||||
describe TETU do
|
||||
it "works" do
|
||||
false.should eq(true)
|
||||
true.should eq(true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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/*"
|
||||
|
|
73
src/components/game/infrastructure.cr
Normal file
73
src/components/game/infrastructure.cr
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
23
src/helpers/ratio.cr
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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,19 +107,32 @@ 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|
|
||||
upgrade = InfrastructureUpgrade.free_instant(id: infra_id)
|
||||
body.infrastructure_upgrades.upgrades << upgrade
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
# 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)
|
||||
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 (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
|
||||
|
|
|
@ -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 ""
|
||||
|
|
Loading…
Reference in New Issue
Block a user