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)
|
./$(NAME)
|
||||||
build:
|
build:
|
||||||
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging
|
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:
|
debug:
|
||||||
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging Dentitas_debug_generator
|
crystal build src/$(NAME).cr --stats --error-trace -Dentitas_enable_logging Dentitas_debug_generator
|
||||||
# I did not enabled --debug because it crashes.
|
# I did not enabled --debug because it crashes.
|
||||||
|
|
22
README.md
22
README.md
|
@ -1,6 +1,6 @@
|
||||||
# TETU Core
|
# 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)
|
[![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
|
# install dependencies first
|
||||||
make deps
|
make deps
|
||||||
|
|
||||||
# make with imgui static linking (I think, I don't remember)
|
# make an optimised build so it's fasteeer
|
||||||
export LD_LIBRARY_PATH="$(pwd)/cimgui"
|
make release
|
||||||
make release
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
# run the
|
|
||||||
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml"
|
export LD_LIBRARY_PATH="$(pwd)/lib/imgui-sfml"
|
||||||
./core
|
./core # run the server (high performance for data)
|
||||||
|
|
||||||
# there is also a make rule that handle the libraries
|
|
||||||
make run
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
* See the wiki <https://git.sceptique.eu/TETU/Core/wiki>.
|
* See the wiki for documentation: <https://git.sceptique.eu/TETU/Core/wiki>.
|
||||||
* See the current kanban: <https://git.sceptique.eu/TETU/Core/projects>
|
* See the current kanban for current WIP: <https://git.sceptique.eu/TETU/Core/projects>.
|
||||||
* Come talk on IRC **irc://irc.sceptique.eu#TETU**
|
* Come talk on IRC: **irc://irc.sceptique.eu:6697#TETU**.
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## 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:
|
c_plant:
|
||||||
<<: *template_default
|
<<: *template_default
|
||||||
title: Chemical plant
|
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:
|
consumes:
|
||||||
mineral:
|
mineral:
|
||||||
function: linear
|
function: linear
|
||||||
|
@ -84,7 +84,7 @@ items:
|
||||||
l_plant:
|
l_plant:
|
||||||
<<: *template_default
|
<<: *template_default
|
||||||
title: Logistic assembly
|
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:
|
consumes:
|
||||||
alloy:
|
alloy:
|
||||||
function: linear
|
function: linear
|
||||||
|
|
|
@ -36,7 +36,7 @@ items:
|
||||||
e_plant:
|
e_plant:
|
||||||
<<: *template_default
|
<<: *template_default
|
||||||
title: Energy plant
|
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:
|
prods:
|
||||||
energy:
|
energy:
|
||||||
function: linear
|
function: linear
|
||||||
|
@ -51,7 +51,7 @@ items:
|
||||||
mine:
|
mine:
|
||||||
<<: *template_default
|
<<: *template_default
|
||||||
title: Mines
|
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:
|
consumes:
|
||||||
energy:
|
energy:
|
||||||
function: linear
|
function: linear
|
||||||
|
@ -62,16 +62,20 @@ items:
|
||||||
function: linear
|
function: linear
|
||||||
coefs:
|
coefs:
|
||||||
a: 10
|
a: 10
|
||||||
|
chemical:
|
||||||
|
function: linear
|
||||||
|
coefs:
|
||||||
|
a: 0.01
|
||||||
wastes:
|
wastes:
|
||||||
pollution:
|
pollution:
|
||||||
function: linear
|
function: linear
|
||||||
coefs:
|
coefs:
|
||||||
a: 1
|
a: 1
|
||||||
|
|
||||||
farm:
|
agrifood:
|
||||||
<<: *template_default
|
<<: *template_default
|
||||||
title: Farmed lands
|
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:
|
consumes:
|
||||||
mineral:
|
mineral:
|
||||||
function: linear
|
function: linear
|
||||||
|
|
|
@ -29,7 +29,7 @@ templates:
|
||||||
max:
|
max:
|
||||||
function: linear
|
function: linear
|
||||||
coefs:
|
coefs:
|
||||||
b: 10_000
|
b: 40_000
|
||||||
|
|
||||||
items:
|
items:
|
||||||
|
|
||||||
|
@ -51,12 +51,12 @@ items:
|
||||||
mineral:
|
mineral:
|
||||||
function: linear
|
function: linear
|
||||||
coefs:
|
coefs:
|
||||||
a: 10000000
|
a: 10000
|
||||||
|
|
||||||
f_store:
|
f_store:
|
||||||
<<: *template_default
|
<<: *template_default
|
||||||
title: Food storage
|
title: Food storage
|
||||||
description: Protective centers for nutrients and food supplies
|
description: Protective centers for nutrients and food supplies.
|
||||||
consumes:
|
consumes:
|
||||||
energy:
|
energy:
|
||||||
function: linear
|
function: linear
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
energy:
|
energy:
|
||||||
name: 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:
|
food:
|
||||||
name: 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:
|
food2:
|
||||||
name: Synthetic Food
|
name: Synthetic Food
|
||||||
description: Artificialy engineered very efficient food
|
description: Artificialy engineered very efficient food and distribution.
|
||||||
mineral:
|
mineral:
|
||||||
name: Simple Minerals
|
name: Simple Minerals in a proper concentration.
|
||||||
minerals2:
|
minerals2:
|
||||||
name: Rare Minerals
|
name: Rare Minerals.
|
||||||
chemical:
|
chemical:
|
||||||
name: Chemicals
|
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
|
require: mineral
|
||||||
alloy:
|
alloy:
|
||||||
name: Alloys
|
name: Alloys
|
||||||
description: Standard alloys
|
description: Standards alloys for most industrial and military usages.
|
||||||
require: mineral
|
require: mineral
|
||||||
alloy2:
|
alloy2:
|
||||||
name: Super Materials
|
name: Super Materials
|
||||||
description: Super alloys with near-magical properties
|
description: Super alloys with near-magical properties compared to modern age.
|
||||||
require: mineral
|
require: mineral
|
||||||
weapon:
|
weapon:
|
||||||
name: Weaponery
|
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
|
require: alloy
|
||||||
logistic:
|
logistic:
|
||||||
name: Logistic
|
name: Logistic
|
||||||
description: Civilan ships for fret and travellers
|
description: Civilan ships and means of transportations for high volume fret and travellers.
|
||||||
pollution:
|
pollution:
|
||||||
name: 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
|
Lampadas
|
||||||
Lankiveil
|
Lankiveil
|
||||||
Lernaeus
|
Lernaeus
|
||||||
Luyten's Star
|
Luyten
|
||||||
Mu Arae
|
Mu Arae
|
||||||
Muritan
|
Muritan
|
||||||
Naraj
|
Naraj
|
||||||
|
@ -53,11 +53,11 @@ Selusa
|
||||||
Sikun
|
Sikun
|
||||||
Sun
|
Sun
|
||||||
Synchrony
|
Synchrony
|
||||||
Trappist
|
|
||||||
Tau Ceti
|
Tau Ceti
|
||||||
Tauri
|
Tauri
|
||||||
Tegeuse
|
Tegeuse
|
||||||
Tleilax
|
Tleilax
|
||||||
|
Trappist
|
||||||
Tupile
|
Tupile
|
||||||
Upsilon Andromedae
|
Upsilon Andromedae
|
||||||
Ursae Majoris
|
Ursae Majoris
|
||||||
|
@ -65,4 +65,3 @@ Virginis
|
||||||
Wasp
|
Wasp
|
||||||
Xo
|
Xo
|
||||||
Yz Ceti
|
Yz Ceti
|
||||||
c
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
require "./spec_helper"
|
require "./spec_helper"
|
||||||
|
|
||||||
describe Core do
|
describe TETU do
|
||||||
# TODO: Write tests
|
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
false.should eq(true)
|
true.should eq(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,79 +18,6 @@ class TETU::Named < Entitas::Component
|
||||||
end
|
end
|
||||||
|
|
||||||
require "./game/resources"
|
require "./game/resources"
|
||||||
|
require "./game/infrastructure"
|
||||||
# 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/*"
|
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)]
|
@[Context(Game)]
|
||||||
class TETU::Population < Entitas::Component
|
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 :amount, Float64, default: 0.0
|
||||||
|
prop :foods, Food, default: DEFAULT_FOOD
|
||||||
|
|
||||||
MIN_RANDOM_POP = 10_000.0
|
MIN_RANDOM_POP = 10.0.millions
|
||||||
MAX_RANDOM_POP = 10_000_000_000.0
|
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
|
entity.add_population amount: (MIN_RANDOM_POP..MAX_RANDOM_POP).sample.round
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s(round : Int32 = 2)
|
||||||
Helpers::Numbers.humanize(number: @amount, round: 2)
|
Helpers::Numbers.humanize(number: @amount, round: round)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,6 +27,10 @@ class TETU::Resources < Entitas::Component
|
||||||
def humanize(sep = "\n")
|
def humanize(sep = "\n")
|
||||||
map { |k, store| "#{k}: #{store.humanize}" }.join(sep)
|
map { |k, store| "#{k}: #{store.humanize}" }.join(sep)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def amount_hash
|
||||||
|
transform_values { |store| store.amount }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# alias Prod = Tuple(Name, Float64)
|
# alias Prod = Tuple(Name, Float64)
|
||||||
|
@ -93,7 +97,7 @@ class TETU::Resources < Entitas::Component
|
||||||
|
|
||||||
def self.default
|
def self.default
|
||||||
stores = Stores.new
|
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
|
infras = Infras.new
|
||||||
|
|
||||||
|
|
|
@ -15,3 +15,17 @@ module TETU::Helpers::Numbers
|
||||||
end
|
end
|
||||||
end
|
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"
|
require "../components"
|
||||||
|
|
||||||
class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
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)
|
def initialize(@contexts : Contexts)
|
||||||
@time_context = @contexts.time
|
@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 = @contexts.game.get_group Entitas::Matcher.all_of(Resources, Population, ManpowerAllocation)
|
||||||
producer_group.entities.each do |e|
|
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|
|
e.resources.infras.each do |infra_id, infra|
|
||||||
|
logger.debug { "#{e.named.to_s} produces now via #{infra.id}" }
|
||||||
rate = prod_rate(infra, e)
|
rate = prod_rate(infra, e)
|
||||||
prod_rates = infra.prods.map { |res, prod| apply_prod(infra: infra, res: res, rate: rate, prod: prod) }
|
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
|
real_prod_rate = prod_rates.empty? ? rate : prod_rates.max
|
||||||
|
@ -42,8 +47,16 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
||||||
end
|
end
|
||||||
|
|
||||||
def prod_rate(infra : Resources::Infra, producer : GameEntity) : Float64
|
def prod_rate(infra : Resources::Infra, producer : GameEntity) : Float64
|
||||||
return 1.0 if infra.consumes.empty?
|
if infra.consumes.empty?
|
||||||
return 0.0 if infra.consumes.any? { |res, _value| infra.stores[res]?.nil? }
|
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
|
# TODO: another function for pop.amount < manpower.optimal
|
||||||
allocated_manpower = producer.manpower_allocation.absolute[infra.id]
|
allocated_manpower = producer.manpower_allocation.absolute[infra.id]
|
||||||
maximal_rate =
|
maximal_rate =
|
||||||
|
@ -54,14 +67,15 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
||||||
end
|
end
|
||||||
limited_rate = (infra.consumes.map { |res, value| infra.stores[res].amount / value } + [maximal_rate]).min
|
limited_rate = (infra.consumes.map { |res, value| infra.stores[res].amount / value } + [maximal_rate]).min
|
||||||
# if infra.id == "mine" || true
|
# if infra.id == "mine" || true
|
||||||
# logger.debug { "producer.named.name=#{producer.named.name}" }
|
logger.debug { "" }
|
||||||
# logger.debug { "infra.id=#{infra.id}" }
|
logger.debug { "producer.named.name=#{producer.named.name}" }
|
||||||
# logger.debug { "allocated_manpower=#{allocated_manpower}" }
|
logger.debug { "infra.id=#{infra.id}" }
|
||||||
# logger.debug { "infra.manpower.optimal=#{infra.manpower.optimal}" }
|
logger.debug { "allocated_manpower=#{allocated_manpower}" }
|
||||||
# logger.debug { "infra.manpower.min=#{infra.manpower.min} " }
|
logger.debug { "infra.manpower.optimal=#{infra.manpower.optimal}" }
|
||||||
# logger.debug { "maximal_rate=#{maximal_rate} " }
|
logger.debug { "infra.manpower.min=#{infra.manpower.min} " }
|
||||||
# logger.debug { "limited_rate=#{limited_rate}" }
|
logger.debug { "maximal_rate=#{maximal_rate} " }
|
||||||
# logger.debug { "" }
|
logger.debug { "limited_rate=#{limited_rate}" }
|
||||||
|
logger.debug { "" }
|
||||||
# end
|
# end
|
||||||
limited_rate
|
limited_rate
|
||||||
end
|
end
|
||||||
|
@ -69,10 +83,10 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
||||||
# returns the real production rate, limited by the storage
|
# returns the real production rate, limited by the storage
|
||||||
# @param rate : the maximum production we should use
|
# @param rate : the maximum production we should use
|
||||||
def apply_prod(infra : Resources::Infra, rate : Float64, res : Resources::Name, prod : Float64) : Float64
|
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]?
|
store = infra.stores[res]?
|
||||||
if store.nil? || rate > 0 && store.amount == store.max
|
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
|
return 0.0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -88,7 +102,7 @@ class TETU::EconomicProductionSystem < Entitas::ReactiveSystem
|
||||||
|
|
||||||
store.amount = new_amount
|
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
|
return rate
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class TETU::GalaxyInitializerSystem
|
class TETU::GalaxyInitializerSystem
|
||||||
include Entitas::Systems::InitializeSystem
|
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
|
def initialize(@contexts : Contexts); end
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ class TETU::GalaxyInitializerSystem
|
||||||
EMPIRE_AMOUNT = AI_AMOUNT + 1 # add the player
|
EMPIRE_AMOUNT = AI_AMOUNT + 1 # add the player
|
||||||
AI_MIN_PLANETS = GALAXY_CONF["ai_start_populated_bodies_amount"].as_i
|
AI_MIN_PLANETS = GALAXY_CONF["ai_start_populated_bodies_amount"].as_i
|
||||||
PLANET_POP_PROBA = TETU::GALAXY_CONF["populated_planets_proba"].as_f
|
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
|
# NO_SPACE_EMPIRE_ID = 100001
|
||||||
|
|
||||||
def init
|
def init
|
||||||
|
@ -44,9 +45,11 @@ class TETU::GalaxyInitializerSystem
|
||||||
|
|
||||||
bodies_amount = Helpers::Planet::BODIES_STATISTICS.sample
|
bodies_amount = Helpers::Planet::BODIES_STATISTICS.sample
|
||||||
bodies_amount = AI_MIN_PLANETS if !empire_id.nil? && bodies_amount < AI_MIN_PLANETS
|
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|
|
bodies_amount.times.map do |index|
|
||||||
body_type = Helpers::Planet::TYPES_STATISTICS.sample
|
body_type = Helpers::Planet::TYPES_STATISTICS.sample
|
||||||
|
body_type = :planet if AI_DEBUG_0_IS_PLANET && index == 0
|
||||||
ids_trash[body_type] += 1
|
ids_trash[body_type] += 1
|
||||||
body = generate_body(star: star, index: index, body_type: body_type, ids_trash: ids_trash)
|
body = generate_body(star: star, index: index, body_type: body_type, ids_trash: ids_trash)
|
||||||
if body_type == :asteroid_belt
|
if body_type == :asteroid_belt
|
||||||
|
@ -104,19 +107,32 @@ class TETU::GalaxyInitializerSystem
|
||||||
body
|
body
|
||||||
end
|
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)
|
def populate(body)
|
||||||
# logger.debug { "populate: #{body.named.name}..." }
|
# logger.debug { "populate: #{body.named.name}..." }
|
||||||
pop_amount = ((10_000.0)..(10_000_000_000.0)).sample
|
pop_amount = ((10_000.0)..(10.0.billions)).sample
|
||||||
body.add_population amount: pop_amount
|
Population.generate_for(body)
|
||||||
body.replace_component(Resources.default_populated)
|
body.replace_component(Resources.default_populated)
|
||||||
body.add_infrastructure_upgrades
|
body.add_infrastructure_upgrades
|
||||||
body.add_manpower_allocation
|
body.add_manpower_allocation
|
||||||
body.manpower_allocation.available = body.population.amount
|
body.manpower_allocation.available = body.population.amount
|
||||||
DEFAULT_INFRASTRUCTURES.each do |infra_id|
|
DEFAULT_INFRASTRUCTURES.each do |infra_id, infra_level|
|
||||||
upgrade = InfrastructureUpgrade.free_instant(id: infra_id)
|
infra_level.times do
|
||||||
body.infrastructure_upgrades.upgrades << upgrade
|
upgrade = InfrastructureUpgrade.free_instant(id: infra_id)
|
||||||
|
body.infrastructure_upgrades.upgrades << upgrade
|
||||||
|
end
|
||||||
end
|
end
|
||||||
# logger.debug { "populated: #{body.named.name}, now #{body.resources.to_s}, with #{body.infrastructure_upgrades.upgrades.size} upgrade to do..." }
|
# logger.debug { "populated: #{body.named.name}, now #{body.resources.to_s}, with #{body.infrastructure_upgrades.upgrades.size} upgrade to do..." }
|
||||||
body
|
body
|
||||||
|
|
|
@ -81,14 +81,14 @@ class TETU::InfrastructureUpgradesSystem < Entitas::ReactiveSystem
|
||||||
end
|
end
|
||||||
|
|
||||||
def pay_upgrade_tick(resources : Resources, upgrade : InfrastructureUpgrade, costs : InfrastructureUpgrade::Costs)
|
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
|
# pay the upgrade with local store
|
||||||
costs.all? { |res, amount| resources.stores[res].amount -= amount }
|
costs.all? { |res, amount| resources.stores[res].amount -= amount }
|
||||||
upgrade.current_tick += 1
|
upgrade.current_tick += 1
|
||||||
logger.debug { "paid tick upgrade" }
|
logger.debug { "paid tick upgrade" }
|
||||||
else
|
else
|
||||||
# if we can't pay the upgrade, we will "loose" one tick due to maintenance
|
# 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
|
upgrade.end_tick += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
require "../components"
|
require "../components"
|
||||||
|
|
||||||
class TETU::PopulationGrowthSystem < Entitas::ReactiveSystem
|
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)
|
def initialize(@contexts : Contexts)
|
||||||
@time_context = @contexts.time
|
@time_context = @contexts.time
|
||||||
|
@ -13,15 +13,62 @@ class TETU::PopulationGrowthSystem < Entitas::ReactiveSystem
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(time_entities : Array(Entitas::IEntity))
|
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|
|
populateds.entities.each do |e|
|
||||||
pop_amount = e.population.amount
|
pop_amount = e.population.amount
|
||||||
# let's say every adult make 1.5 child average in its average lifespan (80 years)
|
foods = e.population.foods
|
||||||
# and one tick is one day
|
pop_foods_needs = foods.transform_values do |food_amount_per_pop|
|
||||||
reproduction_rate = 1.5 * (1.0/80.0) * (1.0/365)
|
food_amount_per_pop * pop_amount
|
||||||
new_pop_amount = pop_amount + pop_amount * reproduction_rate
|
end
|
||||||
# logger.debug { "population growth: {reproduction_rate:#{reproduction_rate}} {population:#{e.population.to_s}} {bonus:#{pop_amount * reproduction_rate}}" }
|
logger.debug { "pop_foods_needs=#{pop_foods_needs}" }
|
||||||
e.replace_population(amount: new_pop_amount)
|
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
|
||||||
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
|
end
|
||||||
|
|
|
@ -8,14 +8,23 @@ class TETU::UiService::PlanetInfrastructure < TETU::UiService
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
if ImGui.tree_node_ex("resources panel", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
if ImGui.tree_node_ex("resources panel", ImGui::ImGuiTreeNodeFlags.new(ImGui::ImGuiTreeNodeFlags::DefaultOpen))
|
||||||
|
ImGui.text @planet.named.name
|
||||||
|
|
||||||
|
draw_population
|
||||||
draw_resources
|
draw_resources
|
||||||
ImGui.tree_pop
|
ImGui.tree_pop
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private def draw_resources
|
private def draw_population
|
||||||
ImGui.text @planet.named.name
|
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?
|
if @planet.has_resources?
|
||||||
draw_storage
|
draw_storage
|
||||||
ImGui.text ""
|
ImGui.text ""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user