Merge branch 'christmas' into 'master'

Christmas

See merge request veloren/veloren!3047
This commit is contained in:
Joshua Barretto 2021-12-20 20:23:26 +00:00
commit 824a978cdb
82 changed files with 1059 additions and 167 deletions

83
Cargo.lock generated
View File

@ -565,6 +565,29 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "chrono-tz"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552"
dependencies = [
"chrono",
"chrono-tz-build",
"phf",
"serde",
]
[[package]]
name = "chrono-tz-build"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069"
dependencies = [
"parse-zoneinfo",
"phf",
"phf_codegen",
]
[[package]]
name = "chumsky"
version = "0.3.2"
@ -3896,6 +3919,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "parse-zoneinfo"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
dependencies = [
"regex",
]
[[package]]
name = "pdqselect"
version = "0.1.0"
@ -3944,6 +3976,45 @@ dependencies = [
"indexmap",
]
[[package]]
name = "phf"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared",
"rand 0.8.4",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher",
"uncased",
]
[[package]]
name = "pin-project-lite"
version = "0.2.7"
@ -5918,6 +5989,15 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "uncased"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
dependencies = [
"version_check 0.9.3",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
@ -6064,6 +6144,8 @@ version = "0.10.0"
dependencies = [
"approx 0.4.0",
"bitflags",
"chrono",
"chrono-tz",
"clap",
"criterion",
"crossbeam-channel",
@ -6292,6 +6374,7 @@ dependencies = [
"authc",
"bincode",
"chrono",
"chrono-tz",
"crossbeam-channel",
"futures-util",
"hashbrown 0.11.2",

View File

@ -0,0 +1,11 @@
EntityConfig (
name: Name("Yeti"),
body: RandomWith("yeti"),
alignment: Alignment(Enemy),
loot: LootTable("common.loot_tables.calendar.christmas.yeti"),
hands: Uninit,
meta: [],
)

View File

@ -0,0 +1,15 @@
ItemDef(
name: "Boreal Warhelmet",
description: "I wonder where it's pointing...",
kind: Armor((
kind: Head("BorealWarhelm"),
stats: (
protection: Some(Normal(9.0)),
poise_resilience: Some(Normal(3.0)),
energy_max: Some(4.0),
energy_reward: Some(0.01),
),
)),
quality: High,
tags: [],
)

View File

@ -1,11 +1,15 @@
ItemDef(
name: "Dark Hood",
description: "yep.",
description: "Tis a bit thicker.",
kind: Armor((
kind: Head("DarkHood"),
stats: (
protection: Some(Normal(7.0)),
poise_resilience: Some(Normal(1.0)),
crit_power: Some(0.02),
stealth: Some(0.1),
),
)),
quality: Common,
quality: Moderate,
tags: [],
)

View File

@ -6,8 +6,8 @@ ItemDef(
stats: (
protection: Some(Normal(4.0)),
energy_max: Some(8.0),
energy_reward: Some(0.12),
crit_power: Some(0.1),
energy_reward: Some(0.1),
crit_power: Some(0.01),
),
)),
quality: High,

View File

@ -4,10 +4,12 @@ ItemDef(
kind: Armor((
kind: Head("Spikeguard"),
stats: (
protection: Some(Normal(2.0)),
poise_resilience: Some(Normal(10.0)),
protection: Some(Normal(9.0)),
poise_resilience: Some(Normal(3.0)),
crit_power: Some(0.01),
stealth: Some(-0.5),
),
)),
quality: Common,
quality: High,
tags: [],
)

View File

@ -0,0 +1,17 @@
ItemDef(
name: "Winged Coronet",
description: "You feel more connected with nature.",
kind: Armor((
kind: Head("WingedCoronet"),
stats: (
protection: Some(Normal(2.0)),
poise_resilience: Some(Normal(0.0)),
energy_max: Some(8.0),
energy_reward: Some(0.05),
crit_power: Some(0.015),
stealth: Some(0.5),
),
)),
quality: High,
tags: [],
)

View File

@ -0,0 +1,15 @@
ItemDef(
name: "Woolly Wintercap",
description: "Simple, stylish, and festive.",
kind: Armor((
kind: Head("WoollyWintercap"),
stats: (
protection: Some(Normal(0.0)),
poise_resilience: Some(Normal(0.0)),
energy_max: Some(4.0),
energy_reward: Some(0.04),
),
)),
quality: Common,
tags: [],
)

View File

@ -0,0 +1,22 @@
ItemDef(
name: "Blue Cheese",
description: "Pungent and filling",
kind: Consumable(
kind: Food,
effects: [
Buff((
kind: Saturation,
data: (
strength: 3.0,
duration: Some((
secs: 20,
nanos: 0,
)),
),
cat_ids: [Natural],
)),
]
),
quality: High,
tags: [Food],
)

View File

@ -0,0 +1,14 @@
ItemDef(
name: "Polaris",
description: "Christmas Lantern.",
kind: Lantern(
(
kind: "PolarisLantern",
color: (r: 67, g: 170, b: 255),
strength_thousandths: 8000,
flicker_thousandths: 600,
),
),
quality: High,
tags: [Utility],
)

View File

@ -8,6 +8,8 @@
Armor(Head): Choice([
(1.0, Some(Item("common.items.armor.misc.head.bandana.thief"))),
(1.0, Some(Item("common.items.armor.misc.head.bandana.red"))),
(1.0, Some(Item("common.items.armor.misc.head.hood"))),
(1.0, Some(Item("common.items.armor.misc.head.hood_dark"))),
]),
Lantern: Choice([

View File

@ -5,10 +5,20 @@
Armor(Hands): Item("common.items.armor.leather_plate.hand"),
Armor(Legs): Item("common.items.armor.leather_plate.pants"),
Armor(Feet): Item("common.items.armor.leather_plate.foot"),
//Armor(Head): Item("common.items.armor.leather_plate.helmet"),
Armor(Head): Choice([
// Christmas event
(2.0, Some(Item("common.items.armor.misc.head.boreal_warhelm"))),
(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
//(4.0, Some(Item("common.items.armor.leather_plate.helmet"))),
]),
Lantern: Choice([
(1.0, Some(Item("common.items.lantern.black_0"))),
(2.0, None),
//(1.0, Some(Item("common.items.lantern.black_0"))),
//(2.0, None),
// Christmas event
(1.0, Some(Item("common.items.lantern.blue_0"))),
(1.0, Some(Item("common.items.lantern.polaris"))),
(2.0, Some(Item("common.items.lantern.red_0"))),
(2.0, Some(Item("common.items.lantern.green_0"))),
]),
})

View File

@ -20,7 +20,9 @@
(1.0, Some(Item("common.items.armor.cloth_blue.foot"))),
]),
Armor(Head): Choice([
(1.0, Some(Item("common.items.armor.misc.head.straw"))),
(1.0, None),
// Christmas event
(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
//(1.0, Some(Item("common.items.armor.misc.head.straw"))),
//(2.0, None),
]),
})

View File

@ -0,0 +1,6 @@
[
(4.0, ItemQuantity("common.items.food.blue_cheese", 1, 30)),
(3.0, Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap")),
(2.0, Item("common.items.armor.misc.head.boreal_warhelm")),
(1.0, Item("common.items.lantern.polaris")),
]

View File

@ -0,0 +1,5 @@
[
(5.0, ItemQuantity("common.items.food.blue_cheese", 1, 10)),
(6.0, ItemQuantity("common.items.mineral.ore.coal", 1, 3)),
(1.0, ItemQuantity("common.items.mineral.gem.diamond", 1, 1)),
]

View File

@ -12,7 +12,8 @@
// Armor
(0.01, Item("common.items.armor.misc.head.bandana.thief")),
(0.01, Item("common.items.armor.misc.head.bandana.red")),
(0.005, Item("common.items.armor.misc.head.hood")),
(0.01, Item("common.items.armor.misc.head.hood")),
(0.01, Item("common.items.armor.misc.head.hood_dark")),
// Food
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
(0.25, LootTable("common.loot_tables.food.prepared")),

View File

@ -296,6 +296,14 @@
],
craft_sprite: Some(Forge),
),
// Only for Christmas event so remove once done
"diamonds": (
output: ("common.items.mineral.gem.diamond", 1),
inputs: [
(Item("common.items.mineral.ore.coal"), 20),
],
craft_sprite: Some(Forge),
),
"cotton": (
output: ("common.items.crafting_ing.cloth.cotton", 1),
inputs: [
@ -1889,6 +1897,15 @@
],
craft_sprite: Some(CraftingBench),
),
"winged coronet": (
output: ("common.items.armor.misc.head.winged_coronet", 1),
inputs: [
(Item("common.items.mineral.gem.emerald"), 1),
(Item("common.items.mineral.ingot.gold"), 4),
(Item("common.items.crafting_ing.animal_misc.raptor_feather"), 2),
],
craft_sprite: Some(CraftingBench),
),
//"metal_blade": (
// output: ("common.items.crafting_ing.modular.damage.sword.metal_blade", 1),
// inputs: [

View File

@ -1053,6 +1053,10 @@
"voxel.lantern.pumpkin",
(0.0, 0.0, 0.0), (-90.0, 120.0, 0.0), 0.9,
),
Lantern("PolarisLantern"): VoxTrans(
"voxel.lantern.polaris",
(0.0, 0.0, 0.0), (-90.0, 120.0, 0.0), 0.9,
),
// Farming Equipment
Tool("common.items.weapons.tool.broom"): VoxTrans(
"voxel.weapon.tool.broom-0",
@ -2231,6 +2235,10 @@
"voxel.armor.misc.head.hood",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 1.3,
),
Armor(Head("DarkHood")): VoxTrans(
"voxel.armor.misc.head.hood_dark",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 1.3,
),
Armor(Head("Crown")): VoxTrans(
"voxel.armor.misc.head.crown",
(0.0, 4.0, 0.0), (-120.0, 210.0,15.0), 1.3,
@ -2243,6 +2251,18 @@
"voxel.armor.misc.head.spikeguard",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 1.3,
),
Armor(Head("WingedCoronet")): VoxTrans(
"voxel.armor.misc.head.winged_coronet",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 1.3,
),
Armor(Head("BorealWarhelm")): VoxTrans(
"voxel.armor.misc.head.boreal_warhelm",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 1.3,
),
Armor(Head("WoollyWintercap")): VoxTrans(
"voxel.armor.misc.head.woolly_wintercap",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 1.3,
),
// Rings
Armor(Ring("Scratched")): VoxTrans(
"voxel.armor.misc.ring.scratched",
@ -2437,6 +2457,10 @@
Consumable("common.items.food.cheese"): Png(
"element.items.item_cheese",
),
Consumable("common.items.food.blue_cheese"): VoxTrans(
"voxel.object.blue_cheese",
(0.0, 0.0, 0.0), (-60.0, -10.0, 0.0), 0.8,
),
Consumable("common.items.food.mushroom"): VoxTrans(
"voxel.sprite.mushrooms.mushroom-10",
(0.0, 0.0, 0.0), (-50.0, 70.0, 40.0), 1.0,

BIN
assets/voxygen/voxel/armor/misc/head/boreal_warhelm.vox (Stored with Git LFS) Executable file

Binary file not shown.

BIN
assets/voxygen/voxel/armor/misc/head/hood_dark.vox (Stored with Git LFS) Normal file → Executable file

Binary file not shown.

BIN
assets/voxygen/voxel/armor/misc/head/winged_coronet.vox (Stored with Git LFS) Executable file

Binary file not shown.

BIN
assets/voxygen/voxel/armor/misc/head/woolly_wintercap.vox (Stored with Git LFS) Executable file

Binary file not shown.

View File

@ -274,7 +274,7 @@
vox_spec: ("armor.pirate.hat", (-3.0, -5.0, 5.0)),
color: None
),
//
// Straw hat
(Human, Male, "Straw"): (
vox_spec: ("armor.misc.head.straw", (-4.0, -5.0, 5.0)),
color: None
@ -717,51 +717,51 @@
),
//
(Human, Male, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-4.0, -4.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-4.0, -6.0, -5.0)),
color: None
),
(Human, Female, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-4.0, -4.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-4.0, -6.0, -5.0)),
color: None
),
(Elf, Male, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -4.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -7.0, -5.0)),
color: None
),
(Elf, Female, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -5.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -7.0, -5.0)),
color: None
),
(Dwarf, Male, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-5.0, -3.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-5.0, -6.0, -5.0)),
color: None
),
(Dwarf, Female, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-5.0, -3.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-5.0, -6.0, -5.0)),
color: None
),
(Danari, Male, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-2.0, -4.0, 1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-2.0, -7.0, -3.0)),
color: None
),
(Danari, Female, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-2.0, -4.0, 1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-2.0, -7.0, -3.0)),
color: None
),
(Undead, Male, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-6.0, -4.0, 1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-6.0, -7.0, -3.0)),
color: None
),
(Undead, Female, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-6.0, -4.0, -0.0)),
vox_spec: ("armor.misc.head.hood_dark", (-6.0, -7.0, -4.0)),
color: None
),
(Orc, Male, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -2.0, 1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -5.0, -4.0)),
color: None
),
(Orc, Female, "DarkHood"): (
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -5.0, -1.0)),
vox_spec: ("armor.misc.head.hood_dark", (-3.0, -8.0, -5.0)),
color: None
),
//
@ -911,6 +911,153 @@
vox_spec: ("armor.merchant.turban_orc", (-1.0, -4.0, 2.0)),
color: None
),
//Winged Coronet -1, 5
(Human, Male, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-4.0, -4.0, -1.0)),
color: None
),
(Human, Female, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-4.0, -3.0, -1.0)),
color: None
),
(Elf, Male, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-3.0, -4.0, -1.0)),
color: None
),
(Elf, Female, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-3.0, -4.0, -1.0)),
color: None
),
(Dwarf, Male, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-5.0, -3.0, -2.5)),
color: None
),
(Dwarf, Female, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-5.0, -3.0, -2.5)),
color: None
),
(Danari, Male, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-2.0, -3.0, 0.0)),
color: None
),
(Danari, Female, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-2.0, -3.0, 0.0)),
color: None
),
(Undead, Male, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-6.0, -5.0, 0.0)),
color: None
),
(Undead, Female, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-6.0, -5.0, -1.0)),
color: None
),
(Orc, Male, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-3.0, -2.0, 1.0)),
color: None
),
(Orc, Female, "WingedCoronet"): (
vox_spec: ("armor.misc.head.winged_coronet", (-3.0, -5.0, -1.0)),
color: None
),
// Boreal Warhelmet
(Human, Male, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-4.0, -5.0, -4.0)),
color: None
),
(Human, Female, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-4.0, -5.0, -4.0)),
color: None
),
(Elf, Male, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-3.0, -6.0, -4.0)),
color: None
),
(Elf, Female, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-3.0, -6.0, -4.0)),
color: None
),
(Dwarf, Male, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-5.0, -5.0, -4.0)),
color: None
),
(Dwarf, Female, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-5.0, -5.0, -4.0)),
color: None
),
(Danari, Male, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-2.0, -6.0, -2.0)),
color: None
),
(Danari, Female, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-2.0, -6.0, -2.0)),
color: None
),
(Undead, Male, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-6.0, -6.0, -3.0)),
color: None
),
(Undead, Female, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-6.0, -6.0, -3.0)),
color: None
),
(Orc, Male, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-3.0, -4.0, -2.0)),
color: None
),
(Orc, Female, "BorealWarhelm"): (
vox_spec: ("armor.misc.head.boreal_warhelm", (-3.0, -7.0, -4.0)),
color: None
),
// Woolly Wintercap (Christmas hat+event)
(Human, Male, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-4.0, -7.0, -3.0)),
color: None
),
(Human, Female, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-4.0, -7.0, -3.0)),
color: None
),
(Elf, Male, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-3.0, -7.0, -4.0)),
color: None
),
(Elf, Female, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-3.0, -8.0, -4.0)),
color: None
),
(Dwarf, Male, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-5.0, -6.0, -3.0)),
color: None
),
(Dwarf, Female, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-5.0, -6.0, -3.0)),
color: None
),
(Danari, Male, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-2.0, -7.0, -2.0)),
color: None
),
(Danari, Female, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-2.0, -7.0, -2.0)),
color: None
),
(Undead, Male, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-6.0, -7.0, -3.0)),
color: None
),
(Undead, Female, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-6.0, -7.0, -4.0)),
color: None
),
(Orc, Male, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-3.0, -5.0, -2.0)),
color: None
),
(Orc, Female, "WoollyWintercap"): (
vox_spec: ("armor.misc.head.woolly_wintercap", (-3.0, -8.0, -4.0)),
color: None
),
}
))

View File

@ -28,5 +28,9 @@
vox_spec: ("lantern.pumpkin", (-3.5, -4.0, -8.5)),
color: None
),
"PolarisLantern": (
vox_spec: ("lantern.polaris", (-3.0, -4.0, -9.5)),
color: None
),
},
))

BIN
assets/voxygen/voxel/lantern/polaris.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/object/blue_cheese.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/furniture/snowflake_light.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/furniture/wreath-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -2015,6 +2015,31 @@ WallLampSmall: Some((
],
wind_sway: 0.0,
)),
ChristmasOrnament: Some((
variations: [
(
model: "voxygen.voxel.sprite.furniture.snowflake_light",
offset: (-5.5, 0.5, 0.0),
lod_axes: (1.0, 1.0, 1.0),
),
(
model: "voxygen.voxel.sprite.furniture.moravian-star-orange",
offset: (-5.5, -7.5, 0.0),
lod_axes: (1.0, 1.0, 1.0),
),
],
wind_sway: 0.2,
)),
ChristmasWreath: Some((
variations: [
(
model: "voxygen.voxel.sprite.furniture.wreath-0",
offset: (-6.5, 0.5, 0.0),
lod_axes: (1.0, 1.0, 1.0),
),
],
wind_sway: 0.0,
)),
// WallSconce
WallSconce: Some((
variations: [

View File

@ -0,0 +1,16 @@
SpawnEntry (
name: "Taiga rare forest wildlife for christmas.",
note: "Search for them in the heart of the taiga, if you are feeling lucky.",
rules: [
Pack(
groups: [
(9, (1, 1, "common.entity.wild.aggressive.wendigo")),
(9, (1, 1, "common.entity.wild.aggressive.dreadhorn")),
(1, (1, 1, "common.entity.calendar.christmas.aggressive.yeti")),
],
is_underwater: false,
calendar_events: Some([Christmas]),
day_period: [Night, Morning, Noon, Evening],
),
],
)

View File

@ -0,0 +1,18 @@
SpawnEntry (
name: "Tundra rare animals for christmas.",
note: "Search for them in the heart of tundra.",
rules: [
Pack(
groups: [
(15, (1, 1, "common.entity.wild.aggressive.snow_raptor")),
(1, (1, 1, "common.entity.wild.aggressive.wendigo")),
(1, (1, 1, "common.entity.wild.aggressive.mammoth")),
(1, (1, 1, "common.entity.wild.aggressive.mountain_troll")),
(1, (1, 1, "common.entity.calendar.christmas.aggressive.yeti")),
],
is_underwater: false,
calendar_events: Some([Christmas]),
day_period: [Night, Morning, Noon, Evening],
),
],
)

View File

@ -0,0 +1,18 @@
SpawnEntry (
name: "Tundra forest animals for christmas.",
note: "",
rules: [
Pack(
groups: [
(4, (1, 1, "common.entity.wild.aggressive.frostfang")),
(4, (1, 1, "common.entity.wild.aggressive.snow_leopard")),
(4, (1, 1, "common.entity.wild.aggressive.yale")),
(4, (1, 1, "common.entity.wild.aggressive.grolgar")),
(1, (1, 1, "common.entity.calendar.christmas.aggressive.yeti")),
],
is_underwater: false,
calendar_events: Some([Christmas]),
day_period: [Night, Morning, Noon, Evening],
),
],
)

View File

@ -0,0 +1,17 @@
SpawnEntry (
name: "Tundra animals for christmas.",
note: "Usually you can find them in snowy areas.",
rules: [
Pack(
groups: [
(6, (1, 3, "common.entity.wild.aggressive.frostfang")),
(6, (1, 3, "common.entity.wild.aggressive.snow_raptor")),
(6, (1, 3, "common.entity.wild.aggressive.roshwalr")),
(1, (1, 1, "common.entity.calendar.christmas.aggressive.yeti")),
],
is_underwater: false,
calendar_events: Some([Christmas]),
day_period: [Night, Morning, Noon, Evening],
),
],
)

View File

@ -1790,8 +1790,9 @@ impl Client {
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
}
},
ServerGeneral::TimeOfDay(time_of_day) => {
ServerGeneral::TimeOfDay(time_of_day, calendar) => {
self.target_time_of_day = Some(time_of_day);
*self.state.ecs_mut().write_resource() = calendar;
},
ServerGeneral::EntitySync(entity_sync_package) => {
self.state

View File

@ -12,6 +12,7 @@ bin_csv = ["ron", "csv", "structopt"]
bin_graphviz = ["petgraph"]
bin_cmd_doc_gen = []
rrt_pathfinding = ["kiddo"]
calendar_events = []
default = ["simd"]
@ -26,6 +27,8 @@ serde = { version = "1.0.110", features = ["derive", "rc"] }
# Util
enum-iterator = "0.7"
vek = { version = "=0.14.1", features = ["serde"] }
chrono = "0.4"
chrono-tz = "0.6"
# Strum
strum = { version = "0.23", features = ["derive"] }

View File

@ -621,6 +621,11 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<AVERAGE_
g: 255,
b: 255,
},
Ice => Rgb {
r: 150,
g: 190,
b: 255,
},
Earth => Rgb {
r: 200,
g: 140,

View File

@ -4,6 +4,7 @@ use super::{
};
use crate::sync;
use common::{
calendar::Calendar,
character::{self, CharacterItem},
comp::{self, invite::InviteKind, item::MaterialStatManifest},
outcome::Outcome,
@ -176,7 +177,7 @@ pub enum ServerGeneral {
ChatMsg(comp::ChatMsg),
ChatMode(comp::ChatMode),
SetPlayerEntity(Uid),
TimeOfDay(TimeOfDay),
TimeOfDay(TimeOfDay, Calendar),
EntitySync(sync::EntitySyncPackage),
CompSync(sync::CompSyncPackage<EcsCompPacket>),
CreateEntity(sync::EntityPackage<EcsCompPacket>),
@ -305,7 +306,7 @@ impl ServerMsg {
| ServerGeneral::ChatMsg(_)
| ServerGeneral::ChatMode(_)
| ServerGeneral::SetPlayerEntity(_)
| ServerGeneral::TimeOfDay(_)
| ServerGeneral::TimeOfDay(_, _)
| ServerGeneral::EntitySync(_)
| ServerGeneral::CompSync(_)
| ServerGeneral::CreateEntity(_)

42
common/src/calendar.rs Normal file
View File

@ -0,0 +1,42 @@
use chrono::{DateTime, Datelike, Local, TimeZone, Utc};
use chrono_tz::Tz;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u16)]
pub enum CalendarEvent {
Christmas = 0,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Calendar {
events: Vec<CalendarEvent>,
}
impl Calendar {
pub fn is_event(&self, event: CalendarEvent) -> bool { self.events.contains(&event) }
pub fn events(&self) -> impl ExactSizeIterator<Item = &CalendarEvent> + '_ {
self.events.iter()
}
pub fn from_events(events: Vec<CalendarEvent>) -> Self { Self { events } }
pub fn from_tz(tz: Option<Tz>) -> Self {
let mut this = Self::default();
let now = match tz {
Some(tz) => {
let utc = Utc::now().naive_utc();
DateTime::<Tz>::from_utc(utc, tz.offset_from_utc_datetime(&utc)).naive_local()
},
None => Local::now().naive_local(),
};
if now.month() == 12 && (20..=30).contains(&now.day()) {
this.events.push(CalendarEvent::Christmas);
}
this
}
}

View File

@ -27,6 +27,8 @@ pub use common_assets as assets;
#[cfg(not(target_arch = "wasm32"))]
mod cached_spatial_grid;
#[cfg(not(target_arch = "wasm32"))]
pub mod calendar;
#[cfg(not(target_arch = "wasm32"))]
pub mod character;
#[cfg(not(target_arch = "wasm32"))] pub mod clock;
#[cfg(not(target_arch = "wasm32"))] pub mod cmd;

View File

@ -305,8 +305,9 @@ pub fn handle_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f3
fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
let accel = if data.physics.on_ground.is_some() {
data.body.base_accel()
let accel = if let Some(block) = data.physics.on_ground {
// FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
} else {
data.body.air_accel()
} * efficiency;
@ -351,7 +352,10 @@ pub fn handle_forced_movement(
match movement {
ForcedMovement::Forward { strength } => {
let strength = strength * data.stats.move_speed_modifier * data.stats.friction_modifier;
if let Some(accel) = data.physics.on_ground.map(|_| data.body.base_accel()) {
if let Some(accel) = data.physics.on_ground.map(|block| {
// FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
}) {
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* (data.inputs.move_dir + Vec2::from(update.ori))

View File

@ -1,6 +1,7 @@
use super::SpriteKind;
use crate::{
comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
consts::FRIC_GROUND,
make_case_elim,
};
use num_derive::FromPrimitive;
@ -49,6 +50,7 @@ make_case_elim!(
Wood = 0x40,
Leaves = 0x41,
GlowingMushroom = 0x42,
Ice = 0x43,
// 0x43 <= x < 0x50 is reserved for future tree parts
// Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we
// often want to experiment with new kinds of block without allocating them a
@ -187,6 +189,7 @@ impl Block {
| SpriteKind::WallLampSmall
| SpriteKind::WallSconce
| SpriteKind::FireBowlGround
| SpriteKind::ChristmasOrnament
| SpriteKind::Orb => Some(16),
SpriteKind::Velorite
| SpriteKind::VeloriteFrag
@ -226,6 +229,7 @@ impl Block {
BlockKind::Leaves => (9, 255),
BlockKind::Wood => (6, 2),
BlockKind::Snow => (6, 2),
BlockKind::Ice => (4, 2),
_ if self.is_opaque() => (0, 255),
_ => (0, 0),
}
@ -251,6 +255,7 @@ impl Block {
BlockKind::Grass => Some(0.5),
BlockKind::WeakRock => Some(0.75),
BlockKind::Snow => Some(0.1),
BlockKind::Ice => Some(0.5),
BlockKind::Lava => None,
_ => self.get_sprite().and_then(|sprite| match sprite {
sprite if sprite.is_container() => None,
@ -291,7 +296,9 @@ impl Block {
#[inline]
pub fn mine_tool(&self) -> Option<ToolKind> {
match self.kind() {
BlockKind::WeakRock | BlockKind::GlowingWeakRock => Some(ToolKind::Pick),
BlockKind::WeakRock | BlockKind::Ice | BlockKind::GlowingWeakRock => {
Some(ToolKind::Pick)
},
_ => self.get_sprite().and_then(|s| s.mine_tool()),
}
}
@ -306,6 +313,29 @@ impl Block {
.unwrap_or(1.0)
}
/// Get the friction constant used to calculate surface friction when
/// walking/climbing. Currently has no units.
#[inline]
pub fn get_friction(&self) -> f32 {
match self.kind() {
BlockKind::Ice => FRIC_GROUND * 0.1,
_ => FRIC_GROUND,
}
}
/// Get the traction permitted by this block as a proportion of the friction
/// applied.
///
/// 1.0 = default, 0.0 = completely inhibits movement, > 1.0 = potential for
/// infinite acceleration (in a vacuum).
#[inline]
pub fn get_traction(&self) -> f32 {
match self.kind() {
BlockKind::Snow => 0.8,
_ => 1.0,
}
}
#[inline]
pub fn kind(&self) -> BlockKind { self.kind }

View File

@ -299,6 +299,11 @@ pub struct MapConfig<'a> {
///
/// Defaults to true.
pub is_water: bool,
/// When `is_water` is true, controls whether an ice layer should appear on
/// that water.
///
/// Defaults to true.
pub is_ice: bool,
/// If true, 3D lighting and shading are turned on. Otherwise, a plain
/// altitude map is used.
///
@ -403,6 +408,7 @@ impl<'a> MapConfig<'a> {
is_basement: false,
is_water: true,
is_ice: true,
is_shaded: true,
is_temperature: false,
is_humidity: false,
@ -581,13 +587,16 @@ impl<'a> MapConfig<'a> {
let g_water = 32.0 * water_color_factor;
let b_water = 64.0 * water_color_factor;
if has_river {
let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8)
.map(|e| e as f64 / 255.0);
rgb = water_rgb;
k_s = Rgb::new(1.0, 1.0, 1.0);
k_d = water_rgb;
k_a = water_rgb;
alpha = 0.255;
// Rudimentary ice check
if !rgb.map(|e| e > 0.35).reduce_and() {
let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8)
.map(|e| e as f64 / 255.0);
rgb = water_rgb;
k_s = Rgb::new(1.0, 1.0, 1.0);
k_d = water_rgb;
k_a = water_rgb;
alpha = 0.255;
}
}
let downhill_alt = sample_wpos(downhill_wpos);

View File

@ -188,6 +188,8 @@ make_case_elim!(
JungleLeafyPlant = 0xA1,
JungleRedGrass = 0xA2,
Bomb = 0xA3,
ChristmasOrnament = 0xA4,
ChristmasWreath = 0xA5,
}
);
@ -451,6 +453,8 @@ impl SpriteKind {
| SpriteKind::TanningRack
| SpriteKind::Loom
| SpriteKind::DismantlingBench
| SpriteKind::ChristmasOrnament
| SpriteKind::ChristmasWreath
)
}
}

View File

@ -5,6 +5,7 @@ use crate::plugin::PluginMgr;
#[cfg(feature = "plugins")]
use common::uid::UidAllocator;
use common::{
calendar::Calendar,
comp,
event::{EventBus, LocalEvent, ServerEvent},
outcome::Outcome,
@ -199,6 +200,7 @@ impl State {
// Register synced resources used by the ECS.
ecs.insert(TimeOfDay(0.0));
ecs.insert(Calendar::default());
// Register unsynced resources used by the ECS.
ecs.insert(Time(0.0));

View File

@ -1692,8 +1692,18 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
physics_state.on_wall = on_wall;
let fric_mod = read.stats.get(entity).map_or(1.0, |s| s.friction_modifier);
if physics_state.on_ground.is_some() || (physics_state.on_wall.is_some() && climbing) {
vel.0 *= (1.0 - FRIC_GROUND.min(1.0) * fric_mod).powf(dt.0 * 60.0);
let ground_fric = physics_state
.on_ground
.map(|b| b.get_friction())
.unwrap_or(0.0);
let wall_fric = if physics_state.on_wall.is_some() && climbing {
FRIC_GROUND
} else {
0.0
};
let fric = ground_fric.max(wall_fric);
if fric > 0.0 {
vel.0 *= (1.0 - fric.min(1.0) * fric_mod).powf(dt.0 * 60.0);
physics_state.ground_vel = ground_vel;
}

View File

@ -41,6 +41,7 @@ rustls = { version = "0.20", default-features = false }
rustls-pemfile = { version = "0.2.1", default-features = false }
atomicwrites = "0.3.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = { version = "0.6", features = ["serde"] }
humantime = "2.1.0"
itertools = "0.10"
lazy_static = "1.4.0"

View File

@ -2,7 +2,8 @@ use crate::metrics::ChunkGenMetrics;
#[cfg(not(feature = "worldgen"))]
use crate::test_world::{IndexOwned, World};
use common::{
generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool, terrain::TerrainChunk,
calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
terrain::TerrainChunk,
};
use hashbrown::{hash_map::Entry, HashMap};
use specs::Entity as EcsEntity;
@ -43,7 +44,7 @@ impl ChunkGenerator {
slowjob_pool: &SlowJobPool,
world: Arc<World>,
index: IndexOwned,
time: TimeOfDay,
time: (TimeOfDay, Calendar),
) {
let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) {
v

View File

@ -123,7 +123,7 @@ impl Client {
| ServerGeneral::ChatMsg(_)
| ServerGeneral::ChatMode(_)
| ServerGeneral::SetPlayerEntity(_)
| ServerGeneral::TimeOfDay(_)
| ServerGeneral::TimeOfDay(_, _)
| ServerGeneral::EntitySync(_)
| ServerGeneral::CompSync(_)
| ServerGeneral::CreateEntity(_)
@ -195,7 +195,7 @@ impl Client {
| ServerGeneral::ChatMsg(_)
| ServerGeneral::ChatMode(_)
| ServerGeneral::SetPlayerEntity(_)
| ServerGeneral::TimeOfDay(_)
| ServerGeneral::TimeOfDay(_, _)
| ServerGeneral::EntitySync(_)
| ServerGeneral::CompSync(_)
| ServerGeneral::CreateEntity(_)

View File

@ -18,6 +18,7 @@ use authc::Uuid;
use chrono::{NaiveTime, Timelike, Utc};
use common::{
assets,
calendar::Calendar,
cmd::{
ChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH, PRESET_MANIFEST_PATH,
},
@ -986,9 +987,14 @@ fn handle_time(
// wait for the next 100th tick to receive the update).
let mut tod_lazymsg = None;
let clients = server.state.ecs().read_storage::<Client>();
let calendar = server.state.ecs().read_resource::<Calendar>();
for client in (&clients).join() {
let msg = tod_lazymsg
.unwrap_or_else(|| client.prepare(ServerGeneral::TimeOfDay(TimeOfDay(new_time))));
let msg = tod_lazymsg.unwrap_or_else(|| {
client.prepare(ServerGeneral::TimeOfDay(
TimeOfDay(new_time),
(*calendar).clone(),
))
});
let _ = client.send_prepared(&msg);
tod_lazymsg = Some(msg);
}
@ -2707,6 +2713,7 @@ fn handle_debug_column(
_action: &ChatCommand,
) -> CmdResult<()> {
let sim = server.world.sim();
let calendar = (*server.state.ecs().read_resource::<Calendar>()).clone();
let sampler = server.world.sample_columns();
let wpos = if let (Some(x), Some(y)) = parse_args!(args, i32, i32) {
Vec2::new(x, y)
@ -2715,7 +2722,7 @@ fn handle_debug_column(
// FIXME: Deal with overflow, if needed.
pos.0.xy().map(|x| x as i32)
};
let msg_generator = || {
let msg_generator = |calendar| {
let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?;
let basement = sim.get_interpolated(wpos, |chunk| chunk.basement)?;
let water_alt = sim.get_interpolated(wpos, |chunk| chunk.water_alt)?;
@ -2727,7 +2734,7 @@ fn handle_debug_column(
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let chunk = sim.get(chunk_pos)?;
let col = sampler.get((wpos, server.index.as_index_ref()))?;
let col = sampler.get((wpos, server.index.as_index_ref(), Some(calendar)))?;
let gradient = sim.get_gradient_approx(chunk_pos)?;
let downhill = chunk.downhill;
let river = &chunk.river;
@ -2766,7 +2773,7 @@ spawn_rate {:?} "#,
spawn_rate
))
};
if let Some(s) = msg_generator() {
if let Some(s) = msg_generator(&calendar) {
server.notify_client(client, ServerGeneral::server_msg(ChatType::CommandInfo, s));
Ok(())
} else {

View File

@ -42,7 +42,7 @@ pub use crate::{
error::Error,
events::Event,
input::Input,
settings::{EditableSettings, Settings},
settings::{CalendarMode, EditableSettings, Settings},
};
#[cfg(feature = "persistent_world")]
@ -64,6 +64,7 @@ use crate::{
use common::grid::Grid;
use common::{
assets::AssetExt,
calendar::Calendar,
character::CharacterId,
cmd::ChatCommand,
comp::{self, item::MaterialStatManifest},
@ -355,6 +356,7 @@ impl Server {
// Load default map from assets.
FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into())
},
calendar: Some(settings.calendar_mode.calendar_now()),
},
state.thread_pool(),
);
@ -593,6 +595,17 @@ impl Server {
self.state.ecs().write_resource::<Tick>().0 += 1;
self.state.ecs().write_resource::<TickStart>().0 = Instant::now();
// Update calendar events as time changes
// TODO: If a lot of calendar events get added, this might become expensive.
// Maybe don't do this every tick?
let new_calendar = self
.state
.ecs()
.read_resource::<Settings>()
.calendar_mode
.calendar_now();
*self.state.ecs_mut().write_resource::<Calendar>() = new_calendar;
// This tick function is the centre of the Veloren universe. Most server-side
// things are managed from here, and as such it's important that it
// stays organised. Please consult the core developers before making
@ -891,7 +904,10 @@ impl Server {
&slow_jobs,
Arc::clone(world),
index.clone(),
*ecs.read_resource::<TimeOfDay>(),
(
*ecs.read_resource::<TimeOfDay>(),
(*ecs.read_resource::<Calendar>()).clone(),
),
);
});
}
@ -1104,7 +1120,10 @@ impl Server {
&slow_jobs,
Arc::clone(&self.world),
self.index.clone(),
*ecs.read_resource::<TimeOfDay>(),
(
*ecs.read_resource::<TimeOfDay>(),
(*ecs.read_resource::<Calendar>()).clone(),
),
);
}

View File

@ -14,7 +14,10 @@ pub use server_description::ServerDescription;
pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
use chrono::Utc;
use common::resources::BattleMode;
use common::{
calendar::{Calendar, CalendarEvent},
resources::BattleMode,
};
use core::time::Duration;
use portpicker::pick_unused_port;
use serde::{Deserialize, Serialize};
@ -62,6 +65,29 @@ impl ServerBattleMode {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CalendarMode {
None,
Auto,
Timezone(chrono_tz::Tz),
Events(Vec<CalendarEvent>),
}
impl Default for CalendarMode {
fn default() -> Self { Self::Auto }
}
impl CalendarMode {
pub fn calendar_now(&self) -> Calendar {
match self {
CalendarMode::None => Calendar::default(),
CalendarMode::Auto => Calendar::from_tz(None),
CalendarMode::Timezone(tz) => Calendar::from_tz(Some(*tz)),
CalendarMode::Events(events) => Calendar::from_events(events.clone()),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Settings {
@ -84,6 +110,7 @@ pub struct Settings {
pub spawn_town: Option<String>,
pub safe_spawn: bool,
pub max_player_for_kill_broadcast: Option<usize>,
pub calendar_mode: CalendarMode,
/// Experimental feature. No guaranteed forwards-compatibility, may be
/// removed at *any time* with no migration.
@ -107,6 +134,7 @@ impl Default for Settings {
max_view_distance: Some(65),
banned_words_files: Vec::new(),
max_player_group_size: 6,
calendar_mode: CalendarMode::Auto,
client_timeout: Duration::from_secs(40),
spawn_town: None,
safe_spawn: true,

View File

@ -8,6 +8,7 @@ use crate::{
wiring, BattleModeBuffer, SpawnPoint,
};
use common::{
calendar::Calendar,
character::CharacterId,
combat,
combat::DamageContributor,
@ -434,7 +435,8 @@ impl StateExt for State {
.for_each(|chunk_key| {
#[cfg(feature = "worldgen")]
{
chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), index.clone(), *ecs.read_resource::<TimeOfDay>());
let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), index.clone(), time);
}
});
}

View File

@ -5,6 +5,7 @@ use crate::{
Tick,
};
use common::{
calendar::Calendar,
comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
outcome::Outcome,
region::{Event as RegionEvent, RegionMap},
@ -28,6 +29,7 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
Read<'a, Tick>,
ReadExpect<'a, TimeOfDay>,
ReadExpect<'a, Calendar>,
ReadExpect<'a, RegionMap>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
@ -61,6 +63,7 @@ impl<'a> System<'a> for Sys {
entities,
tick,
time_of_day,
calendar,
region_map,
uids,
positions,
@ -360,8 +363,9 @@ impl<'a> System<'a> for Sys {
if tick % TOD_SYNC_FREQ == 0 {
let mut tod_lazymsg = None;
for client in (&clients).join() {
let msg = tod_lazymsg
.unwrap_or_else(|| client.prepare(ServerGeneral::TimeOfDay(*time_of_day)));
let msg = tod_lazymsg.unwrap_or_else(|| {
client.prepare(ServerGeneral::TimeOfDay(*time_of_day, (*calendar).clone()))
});
// We don't care much about stream errors here since they could just represent
// network disconnection, which is handled elsewhere.
let _ = client.send_prepared(&msg);

View File

@ -15,6 +15,7 @@ use crate::{
ChunkRequest, SpawnPoint, Tick,
};
use common::{
calendar::Calendar,
comp::{self, agent, bird_medium, BehaviorCapability, ForceUpdate, Pos, Waypoint},
event::{EventBus, ServerEvent},
generation::EntityInfo,
@ -110,6 +111,7 @@ impl<'a> System<'a> for Sys {
Read<'a, SpawnPoint>,
Read<'a, Settings>,
Read<'a, TimeOfDay>,
Read<'a, Calendar>,
ReadExpect<'a, SlowJobPool>,
ReadExpect<'a, IndexOwned>,
ReadExpect<'a, Arc<World>>,
@ -142,6 +144,7 @@ impl<'a> System<'a> for Sys {
spawn_point,
server_settings,
time_of_day,
calendar,
slow_jobs,
index,
world,
@ -176,7 +179,7 @@ impl<'a> System<'a> for Sys {
&slow_jobs,
Arc::clone(&world),
index.clone(),
*time_of_day,
(*time_of_day, calendar.clone()),
)
});

View File

@ -23,6 +23,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
// directly with a closure.
seed_elements: true,
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
calendar: None,
},
&pool,
);

View File

@ -206,7 +206,9 @@ impl MovementEventMapper {
} else {
match underfoot_block_kind {
BlockKind::Snow => SfxEvent::Run(BlockKind::Snow),
BlockKind::Rock | BlockKind::WeakRock => SfxEvent::Run(BlockKind::Rock),
BlockKind::Rock | BlockKind::WeakRock | BlockKind::Ice => {
SfxEvent::Run(BlockKind::Rock)
},
BlockKind::Sand => SfxEvent::Run(BlockKind::Sand),
BlockKind::Air => SfxEvent::Idle,
_ => SfxEvent::Run(BlockKind::Grass),

View File

@ -80,7 +80,7 @@ impl BlocksOfInterest {
},
BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos),
BlockKind::Lava if rng.gen_range(0..5) == 0 => fires.push(pos + Vec3::unit_z()),
BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos),
BlockKind::Snow | BlockKind::Ice if rng.gen_range(0..16) == 0 => snow.push(pos),
_ => match block.get_sprite() {
Some(SpriteKind::Ember) => {
fires.push(pos);

View File

@ -677,6 +677,7 @@ fn main() {
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
calendar: None,
},
&pool,
);

View File

@ -29,6 +29,7 @@ fn main() -> Result {
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
calendar: None,
},
&pool,
);

View File

@ -124,6 +124,7 @@ fn main() {
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
calendar: None,
},
&pool,
);

View File

@ -166,6 +166,7 @@ fn main() {
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
calendar: None,
},
&pool,
);

View File

@ -35,7 +35,7 @@ fn main() {
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
let (alt, place) = sampler
.get((pos, index))
.get((pos, index, None))
.map(|sample| {
(
sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8,

View File

@ -49,6 +49,7 @@ fn main() {
world_file: sim::FileOpts::LoadAsset(veloren_world::sim::DEFAULT_WORLD_MAP.into()),
/* world_file: sim::FileOpts::Load(_map_file),
* world_file: sim::FileOpts::Save(sim::SizeOpts::default()), */
calendar: None,
},
&threadpool,
);
@ -66,6 +67,7 @@ fn main() {
uniform_idx_as_vec2(map_size_lg, posi)
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
index,
None,
))
})
.collect::<Vec<_>>()
@ -177,6 +179,7 @@ fn main() {
is_basement,
is_water,
is_ice: true,
is_shaded,
is_temperature,
is_humidity,

View File

@ -67,6 +67,7 @@ fn generate(db_path: &str, ymin: Option<i32>, ymax: Option<i32>) -> Result<(), B
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
calendar: None,
},
&pool,
);

View File

@ -1,11 +1,14 @@
use crate::{
column::{ColumnGen, ColumnSample},
util::{FastNoise, RandomField, RandomPerm, Sampler, SmallCache},
IndexRef,
IndexRef, CONFIG,
};
use common::terrain::{
structure::{self, StructureBlock},
Block, BlockKind, SpriteKind,
use common::{
calendar::{Calendar, CalendarEvent},
terrain::{
structure::{self, StructureBlock},
Block, BlockKind, SpriteKind,
},
};
use core::ops::{Div, Mul, Range};
use serde::Deserialize;
@ -33,19 +36,25 @@ impl<'a> BlockGen<'a> {
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
wpos: Vec2<i32>,
index: IndexRef<'a>,
calendar: Option<&'a Calendar>,
) -> Option<&'b ColumnSample<'a>> {
cache
.get(wpos, |wpos| column_gen.get((wpos, index)))
.get(wpos, |wpos| column_gen.get((wpos, index, calendar)))
.as_ref()
}
pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: IndexRef<'a>) -> Option<ZCache<'a>> {
pub fn get_z_cache(
&mut self,
wpos: Vec2<i32>,
index: IndexRef<'a>,
calendar: Option<&'a Calendar>,
) -> Option<ZCache<'a>> {
let BlockGen { column_gen } = self;
// Main sample
let sample = column_gen.get((wpos, index))?;
let sample = column_gen.get((wpos, index, calendar))?;
Some(ZCache { sample })
Some(ZCache { sample, calendar })
}
pub fn get_with_z_cache(&mut self, wpos: Vec3<i32>, z_cache: Option<&ZCache>) -> Option<Block> {
@ -65,8 +74,9 @@ impl<'a> BlockGen<'a> {
//tree_density,
//forest_kind,
//close_structures,
// marble,
// marble_small,
marble: _,
marble_mid: _,
marble_small: _,
rock,
// temp,
// humidity,
@ -74,6 +84,8 @@ impl<'a> BlockGen<'a> {
snow_cover,
cliff_offset,
cliff_height,
// water_vel,
ice_depth,
..
} = sample;
@ -195,8 +207,11 @@ impl<'a> BlockGen<'a> {
}
})
.or_else(|| {
let over_water = height < water_height;
// Water
if (wposf.z as f32) < water_height {
if over_water && (wposf.z as f32 - water_height).abs() < ice_depth {
Some(Block::new(BlockKind::Ice, CONFIG.ice_color))
} else if (wposf.z as f32) < water_height {
// Ocean
Some(water)
} else {
@ -208,6 +223,7 @@ impl<'a> BlockGen<'a> {
pub struct ZCache<'a> {
pub sample: ColumnSample<'a>,
pub calendar: Option<&'a Calendar>,
}
impl<'a> ZCache<'a> {
@ -223,7 +239,7 @@ impl<'a> ZCache<'a> {
let ground_max = self.sample.alt + warp + rocks + 2.0;
let max = ground_max.max(self.sample.water_level + 2.0);
let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth);
(min, max)
}
@ -237,6 +253,7 @@ pub fn block_from_structure(
structure_seed: u32,
sample: &ColumnSample,
mut with_sprite: impl FnMut(SpriteKind) -> Block,
calendar: Option<&Calendar>,
) -> Option<Block> {
let field = RandomField::new(structure_seed);
@ -312,15 +329,21 @@ pub fn block_from_structure(
};
range.map(|range| {
Block::new(
BlockKind::Leaves,
Rgb::<f32>::lerp(
Rgb::<u8>::from(range.start).map(f32::from),
Rgb::<u8>::from(range.end).map(f32::from),
lerp,
if calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas))
&& field.chance(pos + structure_pos, 0.025)
{
Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0))
} else {
Block::new(
BlockKind::Leaves,
Rgb::<f32>::lerp(
Rgb::<u8>::from(range.start).map(f32::from),
Rgb::<u8>::from(range.end).map(f32::from),
lerp,
)
.map(|e| e as u8),
)
.map(|e| e as u8),
)
}
})
},
StructureBlock::BirchWood => {

View File

@ -8,6 +8,7 @@ use crate::{
util::{Grid, Sampler},
};
use common::{
calendar::Calendar,
generation::EntityInfo,
terrain::{Block, BlockKind, Structure, TerrainChunk, TerrainChunkSize},
vol::{ReadVol, RectVolSize, WriteVol},
@ -24,9 +25,12 @@ pub struct CanvasInfo<'a> {
pub(crate) chunks: &'a WorldSim,
pub(crate) index: IndexRef<'a>,
pub(crate) chunk: &'a SimChunk,
pub(crate) calendar: Option<&'a Calendar>,
}
impl<'a> CanvasInfo<'a> {
pub fn calendar(&self) -> Option<&'a Calendar> { self.calendar }
pub fn wpos(&self) -> Vec2<i32> { self.wpos }
pub fn area(&self) -> Aabr<i32> {
@ -51,9 +55,11 @@ impl<'a> CanvasInfo<'a> {
/// This function does not (currently) cache generated columns.
pub fn col_or_gen(&self, wpos: Vec2<i32>) -> Option<Cow<'a, ColumnSample>> {
self.col(wpos).map(Cow::Borrowed).or_else(|| {
Some(Cow::Owned(
ColumnGen::new(self.chunks()).get((wpos, self.index()))?,
))
Some(Cow::Owned(ColumnGen::new(self.chunks()).get((
wpos,
self.index(),
self.calendar,
))?))
})
}
@ -122,6 +128,7 @@ impl<'a> CanvasInfo<'a> {
chunks: sim,
index,
chunk: &sim_chunk,
calendar: None,
})
}
}
@ -221,6 +228,7 @@ impl<'a> Canvas<'a> {
seed,
col,
|sprite| block.with_sprite(sprite),
info.calendar,
) {
if !new_block.is_air() {
if with_snow && col.snow_cover && above {

View File

@ -5,6 +5,7 @@ use crate::{
IndexRef, CONFIG,
};
use common::{
calendar::{Calendar, CalendarEvent},
terrain::{
quadratic_nearest_point, river_spline_coeffs, uniform_idx_as_vec2, vec2_as_uniform_idx,
TerrainChunkSize,
@ -64,10 +65,10 @@ impl<'a> ColumnGen<'a> {
}
impl<'a> Sampler<'a> for ColumnGen<'a> {
type Index = (Vec2<i32>, IndexRef<'a>);
type Index = (Vec2<i32>, IndexRef<'a>, Option<&'a Calendar>);
type Sample = Option<ColumnSample<'a>>;
fn get(&self, (wpos, index): Self::Index) -> Option<ColumnSample<'a>> {
fn get(&self, (wpos, index, calendar): Self::Index) -> Option<ColumnSample<'a>> {
let wposf = wpos.map(|e| e as f64);
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
@ -90,6 +91,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
wpos,
|chunk| if chunk.river.near_water() { 1.0 } else { 0.0 },
)?;
let water_vel = sim.get_interpolated(wpos, |chunk| {
if chunk.river.river_kind.is_some() {
chunk.river.velocity
} else {
Vec3::zero()
}
})?;
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?;
let sim_chunk = sim.get(chunk_pos)?;
@ -1079,24 +1087,29 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
);
// Snow covering
let snow_cover = temp
.sub(CONFIG.snow_temp)
let thematic_snow = calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas));
let snow_factor = temp
.sub(if thematic_snow {
CONFIG.tropical_temp
} else {
CONFIG.snow_temp
})
.max(-humidity.sub(CONFIG.desert_hum))
.mul(4.0)
.add(((marble - 0.5) / 0.5) * 0.5)
.add(((marble_mid - 0.5) / 0.5) * 0.25)
.add(((marble_small - 0.5) / 0.5) * 0.175);
let (alt, ground, sub_surface_color) = if snow_cover <= 0.0 && alt > water_level {
let (alt, ground, sub_surface_color) = if snow_factor <= 0.0 && alt > water_level {
// Allow snow cover.
(
alt + 1.0 - snow_cover.max(0.0),
Rgb::lerp(snow, ground, snow_cover),
alt + 1.0 - snow_factor.max(0.0),
Rgb::lerp(snow, ground, snow_factor),
Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)),
)
} else {
(alt, ground, sub_surface_color)
};
let snow_cover = snow_cover <= 0.0;
let snow_cover = snow_factor <= 0.0;
// Make river banks not have grass
let ground = water_dist
@ -1110,6 +1123,29 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let path = sim.get_nearest_path(wpos);
let cave = sim.get_nearest_cave(wpos);
let ice_depth = if snow_factor < -0.25
&& water_vel.magnitude_squared() < (0.1f32 + marble_mid * 0.2).powi(2)
{
let cliff = (sim.gen_ctx.hill_nz.get((wposf3d.div(180.0)).into_array()) as f32)
.add((marble_mid - 0.5) * 0.2)
.abs()
.powi(3)
.mul(32.0);
let cliff_ctrl = (sim.gen_ctx.hill_nz.get((wposf3d.div(128.0)).into_array()) as f32)
.sub(0.4)
.add((marble_mid - 0.5) * 0.2)
.mul(32.0)
.clamped(0.0, 1.0);
(((1.0 - Lerp::lerp(marble, Lerp::lerp(marble_mid, marble_small, 0.25), 0.5)) * 5.0
- 1.5)
.max(0.0)
+ cliff * cliff_ctrl)
.min((water_level - alt).max(0.0))
} else {
0.0
};
Some(ColumnSample {
alt,
riverless_alt,
@ -1143,6 +1179,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
},
forest_kind: sim_chunk.forest_kind,
marble,
marble_mid,
marble_small,
rock,
temp,
@ -1156,6 +1193,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
snow_cover,
cliff_offset,
cliff_height,
water_vel,
ice_depth,
chunk: sim_chunk,
})
@ -1175,6 +1214,7 @@ pub struct ColumnSample<'a> {
pub tree_density: f32,
pub forest_kind: ForestKind,
pub marble: f32,
pub marble_mid: f32,
pub marble_small: f32,
pub rock: f32,
pub temp: f32,
@ -1188,6 +1228,8 @@ pub struct ColumnSample<'a> {
pub snow_cover: bool,
pub cliff_offset: f32,
pub cliff_height: f32,
pub water_vel: Vec3<f32>,
pub ice_depth: f32,
pub chunk: &'a SimChunk,
}

View File

@ -1,5 +1,6 @@
use common::assets;
use serde::Deserialize;
use vek::*;
pub struct Config {
pub sea_level: f32,
@ -51,6 +52,8 @@ pub struct Config {
/// Rough desired river width-to-depth ratio (in terms of horizontal chunk
/// width / m, for some reason). Not exact.
pub river_width_to_depth: f32,
/// TODO: Move to colors.ron when blockgen can access it
pub ice_color: Rgb<u8>,
}
pub const CONFIG: Config = Config {
@ -71,6 +74,7 @@ pub const CONFIG: Config = Config {
river_max_width: 2.0,
river_min_height: 0.25,
river_width_to_depth: 8.0,
ice_color: Rgb::new(140, 175, 255),
};
#[derive(Deserialize)]

View File

@ -7,6 +7,7 @@ use crate::{
};
use common::{
assets::AssetHandle,
calendar::{Calendar, CalendarEvent},
terrain::{
structure::{Structure, StructureBlock, StructuresGroup},
Block, BlockKind, SpriteKind,
@ -33,7 +34,11 @@ static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052);
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
pub fn apply_trees_to(
canvas: &mut Canvas,
dynamic_rng: &mut impl Rng,
calendar: Option<&Calendar>,
) {
// TODO: Get rid of this
#[allow(clippy::large_enum_variant)]
enum TreeModel {
@ -63,7 +68,7 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
} in trees
{
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
let col = ColumnGen::new(info.chunks()).get((pos, info.index()))?;
let col = ColumnGen::new(info.chunks()).get((pos, info.index(), calendar))?;
// Ensure that it's valid to place a *thing* here
if col.alt < col.water_level
@ -135,7 +140,11 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
ForestKind::Pine => {
break 'model TreeModel::Procedural(
ProceduralTree::generate(
TreeConfig::pine(&mut RandomPerm::new(seed), scale),
TreeConfig::pine(
&mut RandomPerm::new(seed),
scale,
calendar,
),
&mut RandomPerm::new(seed),
),
StructureBlock::PineLeaves,
@ -256,6 +265,7 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
tree.seed,
col,
Block::air,
calendar,
)
.map(|block| {
// Add lights to the tree
@ -547,7 +557,7 @@ impl TreeConfig {
}
}
pub fn pine(rng: &mut impl Rng, scale: f32) -> Self {
pub fn pine(rng: &mut impl Rng, scale: f32, calendar: Option<&Calendar>) -> Self {
let scale = scale * (1.0 + rng.gen::<f32>().powi(4) * 0.5);
let log_scale = 1.0 + scale.log2().max(0.0);
@ -567,7 +577,11 @@ impl TreeConfig {
leaf_vertical_scale: 0.3,
proportionality: 1.0,
inhabited: false,
hanging_sprites: &[(0.0001, SpriteKind::Beehive)],
hanging_sprites: if calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas)) {
&[(0.0001, SpriteKind::Beehive), (0.01, SpriteKind::Orb)]
} else {
&[(0.0001, SpriteKind::Beehive)]
},
trunk_block: StructureBlock::Filled(BlockKind::Wood, Rgb::new(90, 35, 15)),
}
}

View File

@ -1,6 +1,7 @@
use crate::{column::ColumnSample, sim::SimChunk, IndexRef, CONFIG};
use common::{
assets::{self, AssetExt},
calendar::{Calendar, CalendarEvent},
generation::{ChunkSupplement, EntityInfo},
resources::TimeOfDay,
terrain::Block,
@ -40,7 +41,12 @@ impl assets::Asset for SpawnEntry {
impl SpawnEntry {
pub fn from(asset_specifier: &str) -> Self { Self::load_expect_cloned(asset_specifier) }
pub fn request(&self, requested_period: DayPeriod, underwater: bool) -> Option<Pack> {
pub fn request(
&self,
requested_period: DayPeriod,
calendar: Option<&Calendar>,
underwater: bool,
) -> Option<Pack> {
self.rules
.iter()
.find(|pack| {
@ -48,8 +54,15 @@ impl SpawnEntry {
.day_period
.iter()
.any(|period| *period == requested_period);
let calendar_match = if let Some(calendar) = calendar {
pack.calendar_events.as_ref().map_or(true, |events| {
events.iter().any(|event| calendar.is_event(*event))
})
} else {
false
};
let water_match = pack.is_underwater == underwater;
time_match && water_match
time_match && calendar_match && water_match
})
.cloned()
}
@ -97,6 +110,9 @@ pub struct Pack {
pub groups: Vec<(Weight, (Min, Max, String))>,
pub is_underwater: bool,
pub day_period: Vec<DayPeriod>,
#[serde(default)]
pub calendar_events: Option<Vec<CalendarEvent>>, /* None implies that the group isn't
* limited by calendar events */
}
impl Pack {
@ -128,19 +144,46 @@ pub fn spawn_manifest() -> Vec<(&'static str, DensityFn)> {
("world.wildlife.spawn.tundra.core", |c, _col| {
close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * 0.5
}),
// Core animals events
(
"world.wildlife.spawn.calendar.christmas.tundra.core",
|c, _col| close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * 0.5,
),
// Snowy animals
("world.wildlife.spawn.tundra.snow", |c, col| {
close(c.temp, CONFIG.snow_temp, 0.3) * BASE_DENSITY * col.snow_cover as i32 as f32 * 1.0
}),
// Snowy animals event
(
"world.wildlife.spawn.calendar.christmas.tundra.snow",
|c, col| {
close(c.temp, CONFIG.snow_temp, 0.3)
* BASE_DENSITY
* col.snow_cover as i32 as f32
* 1.0
},
),
// Forest animals
("world.wildlife.spawn.tundra.forest", |c, col| {
close(c.temp, CONFIG.snow_temp, 0.3) * col.tree_density * BASE_DENSITY * 1.4
}),
// Forest animals event
(
"world.wildlife.spawn.calendar.christmas.tundra.forest",
|c, col| close(c.temp, CONFIG.snow_temp, 0.3) * col.tree_density * BASE_DENSITY * 1.4,
),
// **Taiga**
// Forest core animals
("world.wildlife.spawn.taiga.core_forest", |c, col| {
close(c.temp, CONFIG.snow_temp + 0.2, 0.2) * col.tree_density * BASE_DENSITY * 0.4
}),
// Forest core animals event
(
"world.wildlife.spawn.calendar.christmas.taiga.core_forest",
|c, col| {
close(c.temp, CONFIG.snow_temp + 0.2, 0.2) * col.tree_density * BASE_DENSITY * 0.4
},
),
// Core animals
("world.wildlife.spawn.taiga.core", |c, _col| {
close(c.temp, CONFIG.snow_temp + 0.2, 0.2) * BASE_DENSITY * 1.0
@ -271,7 +314,7 @@ pub fn apply_wildlife_supplement<'a, R: Rng>(
index: IndexRef,
chunk: &SimChunk,
supplement: &mut ChunkSupplement,
time: Option<TimeOfDay>,
time: Option<&(TimeOfDay, Calendar)>,
) {
let scatter = &index.wildlife_spawns;
@ -289,12 +332,11 @@ pub fn apply_wildlife_supplement<'a, R: Rng>(
};
let underwater = col_sample.water_level > col_sample.alt;
let current_day_period;
if let Some(time) = time {
current_day_period = DayPeriod::from(time.0)
let (current_day_period, calendar) = if let Some((time, calendar)) = time {
(DayPeriod::from(time.0), Some(calendar))
} else {
current_day_period = DayPeriod::Noon
}
(DayPeriod::Noon, None)
};
let entity_group = scatter
.iter()
@ -305,7 +347,7 @@ pub fn apply_wildlife_supplement<'a, R: Rng>(
.then(|| {
entry
.read()
.request(current_day_period, underwater)
.request(current_day_period, calendar, underwater)
.and_then(|pack| {
(dynamic_rng.gen::<f32>() < density * col_sample.spawn_rate
&& col_sample.gradient < Some(1.3))

View File

@ -49,6 +49,7 @@ use crate::{
};
use common::{
assets,
calendar::Calendar,
generation::{ChunkSupplement, EntityInfo},
resources::TimeOfDay,
terrain::{
@ -178,14 +179,17 @@ impl World {
}),
)
.collect(),
..self.sim.get_map(index)
..self.sim.get_map(index, self.sim().calendar.as_ref())
}
})
}
pub fn sample_columns(
&self,
) -> impl Sampler<Index = (Vec2<i32>, IndexRef), Sample = Option<ColumnSample>> + '_ {
) -> impl Sampler<
Index = (Vec2<i32>, IndexRef, Option<&'_ Calendar>),
Sample = Option<ColumnSample>,
> + '_ {
ColumnGen::new(&self.sim)
}
@ -215,8 +219,10 @@ impl World {
chunk_pos: Vec2<i32>,
// TODO: misleading name
mut should_continue: impl FnMut() -> bool,
time: Option<TimeOfDay>,
time: Option<(TimeOfDay, Calendar)>,
) -> Result<(TerrainChunk, ChunkSupplement), ()> {
let calendar = time.as_ref().map(|(_, cal)| cal);
let mut sampler = self.sample_blocks();
let chunk_wpos2d = chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
@ -224,7 +230,7 @@ impl World {
let grid_border = 4;
let zcache_grid = Grid::populate_from(
TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2,
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, index),
|offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs, index, calendar),
);
let air = Block::air(SpriteKind::Empty);
@ -347,6 +353,7 @@ impl World {
chunks: &self.sim,
index,
chunk: sim_chunk,
calendar,
},
chunk: &mut chunk,
entities: Vec::new(),
@ -362,7 +369,7 @@ impl World {
layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng);
}
if index.features.trees {
layer::apply_trees_to(&mut canvas, &mut dynamic_rng);
layer::apply_trees_to(&mut canvas, &mut dynamic_rng, calendar);
}
if index.features.scatter {
layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
@ -424,7 +431,7 @@ impl World {
index,
sim_chunk,
&mut supplement,
time,
time.as_ref(),
);
// Apply site supplementary information

View File

@ -81,6 +81,7 @@ pub fn sample_pos(
is_basement,
is_water,
is_ice,
is_shaded,
is_temperature,
is_humidity,
@ -133,7 +134,7 @@ pub fn sample_pos(
let humidity = humidity.min(1.0).max(0.0);
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
let column_rgb_alt = samples
let column_data = samples
.and_then(|samples| {
chunk_idx
.and_then(|chunk_idx| samples.get(chunk_idx))
@ -162,14 +163,14 @@ pub fn sample_pos(
.map(|e| e as f64)
};
(rgb, alt)
(rgb, alt, sample.ice_depth)
});
let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
let alt = if is_basement {
basement
} else {
column_rgb_alt.map_or(alt, |(_, alt)| alt)
column_data.map_or(alt, |(_, alt, _)| alt)
};
let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;
@ -189,7 +190,7 @@ pub fn sample_pos(
if is_shaded { 1.0 } else { alt },
if is_shaded || is_humidity { 1.0 } else { 0.0 },
);
let column_rgb = column_rgb_alt.map(|(rgb, _)| rgb).unwrap_or(default_rgb);
let column_rgb = column_data.map(|(rgb, _, _)| rgb).unwrap_or(default_rgb);
let mut connections = [None; 8];
let mut has_connections = false;
// TODO: Support non-river connections.
@ -213,33 +214,38 @@ pub fn sample_pos(
});
});
};
let rgb = match (river_kind, (is_water, true_alt >= true_sea_level)) {
(_, (false, _)) | (None, (_, true)) | (Some(RiverKind::River { .. }), _) => {
let (r, g, b) = (
(column_rgb.r
* if is_temperature {
temperature as f64
} else {
column_rgb.r
})
.sqrt(),
column_rgb.g,
(column_rgb.b
* if is_humidity {
humidity as f64
} else {
column_rgb.b
})
.sqrt(),
);
Rgb::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
},
(None | Some(RiverKind::Lake { .. } | RiverKind::Ocean), _) => Rgb::new(
0,
((g_water - water_depth * g_water) * 1.0) as u8,
((b_water - water_depth * b_water) * 1.0) as u8,
),
};
let rgb =
if is_water && is_ice && column_data.map_or(false, |(_, _, ice_depth)| ice_depth > 0.0) {
CONFIG.ice_color
} else {
match (river_kind, (is_water, true_alt >= true_sea_level)) {
(_, (false, _)) | (None, (_, true)) | (Some(RiverKind::River { .. }), _) => {
let (r, g, b) = (
(column_rgb.r
* if is_temperature {
temperature as f64
} else {
column_rgb.r
})
.sqrt(),
column_rgb.g,
(column_rgb.b
* if is_humidity {
humidity as f64
} else {
column_rgb.b
})
.sqrt(),
);
Rgb::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
},
(None | Some(RiverKind::Lake { .. } | RiverKind::Ocean), _) => Rgb::new(
0,
((g_water - water_depth * g_water) * 1.0) as u8,
((b_water - water_depth * b_water) * 1.0) as u8,
),
}
};
// TODO: Make principled.
let rgb = if is_path {
Rgb::new(0x37, 0x29, 0x23)

View File

@ -40,6 +40,7 @@ use crate::{
};
use common::{
assets::{self, AssetExt},
calendar::Calendar,
grid::Grid,
lottery::Lottery,
spiral::Spiral2d,
@ -183,6 +184,7 @@ pub struct WorldOpts {
/// Set to false to disable seeding elements during worldgen.
pub seed_elements: bool,
pub world_file: FileOpts,
pub calendar: Option<Calendar>,
}
impl Default for WorldOpts {
@ -190,6 +192,7 @@ impl Default for WorldOpts {
Self {
seed_elements: true,
world_file: Default::default(),
calendar: None,
}
}
}
@ -385,13 +388,17 @@ pub struct WorldSim {
pub(crate) gen_ctx: GenCtx,
pub rng: ChaChaRng,
pub(crate) calendar: Option<Calendar>,
}
impl WorldSim {
pub fn generate(seed: u32, opts: WorldOpts, threadpool: &rayon::ThreadPool) -> Self {
let calendar = opts.calendar; // separate lifetime of elements
let world_file = opts.world_file;
// Parse out the contents of various map formats into the values we need.
let parsed_world_file = (|| {
let map = match opts.world_file {
let map = match world_file {
FileOpts::LoadLegacy(ref path) => {
let file = match File::open(path) {
Ok(file) => file,
@ -491,7 +498,7 @@ impl WorldSim {
},
})
.unwrap_or_else(|| {
let size_lg = match opts.world_file {
let size_lg = match world_file {
FileOpts::Generate(SizeOpts { x_lg, y_lg, .. })
| FileOpts::Save(SizeOpts { x_lg, y_lg, .. }) => {
MapSizeLg::new(Vec2 { x: x_lg, y: y_lg }).unwrap_or_else(|e| {
@ -506,7 +513,7 @@ impl WorldSim {
let continent_scale_hack = if let Some(map) = &parsed_world_file {
map.continent_scale_hack
} else if let FileOpts::Generate(SizeOpts { scale, .. })
| FileOpts::Save(SizeOpts { scale, .. }) = opts.world_file
| FileOpts::Save(SizeOpts { scale, .. }) = world_file
{
scale
} else {
@ -1139,7 +1146,7 @@ impl WorldSim {
basement,
});
(|| {
if let FileOpts::Save { .. } = opts.world_file {
if let FileOpts::Save { .. } = world_file {
use std::time::SystemTime;
// Check if folder exists and create it if it does not
let mut path = PathBuf::from("./maps");
@ -1442,6 +1449,7 @@ impl WorldSim {
_locations: Vec::new(),
gen_ctx,
rng,
calendar,
};
this.generate_cliffs();
@ -1460,7 +1468,7 @@ impl WorldSim {
/// Draw a map of the world based on chunk information. Returns a buffer of
/// u32s.
pub fn get_map(&self, index: IndexRef) -> WorldMapMsg {
pub fn get_map(&self, index: IndexRef, calendar: Option<&Calendar>) -> WorldMapMsg {
let mut map_config = MapConfig::orthographic(
self.map_size_lg(),
core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height),
@ -1485,8 +1493,11 @@ impl WorldSim {
|| Box::new(BlockGen::new(ColumnGen::new(self))),
|_block_gen, posi| {
let sample = column_sample.get(
(uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
index)
(
uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
index,
calendar,
)
)?;
// sample.water_level = CONFIG.sea_level.max(sample.water_level);

View File

@ -1182,6 +1182,7 @@ mod tests {
seed_elements: true,
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
//sim::FileOpts::LoadAsset("world.map.economy_8x8".into()),
calendar: None,
};
let mut index = crate::index::Index::new(seed);
info!("Index created");

View File

@ -7,6 +7,7 @@ use crate::{
IndexRef,
};
use common::{
calendar::{Calendar, CalendarEvent},
make_case_elim,
terrain::{Block, BlockKind, SpriteKind},
};
@ -107,6 +108,7 @@ pub struct House {
pub noise: RandomField,
pub roof_ribbing: bool,
pub roof_ribbing_diagonal: bool,
pub christmas_decorations: bool,
}
#[derive(Copy, Clone)]
@ -180,7 +182,7 @@ impl Attr {
impl Archetype for House {
type Attr = Attr;
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
fn generate<R: Rng>(rng: &mut R, calendar: Option<&Calendar>) -> (Self, Skeleton<Self::Attr>) {
let len = rng.gen_range(-8..24).clamped(0, 20);
let locus = 6 + rng.gen_range(0..5);
let branches_per_side = 1 + len as usize / 20;
@ -239,6 +241,9 @@ impl Archetype for House {
noise: RandomField::new(rng.gen()),
roof_ribbing: rng.gen(),
roof_ribbing_diagonal: rng.gen(),
christmas_decorations: calendar
.map(|c| c.is_event(CalendarEvent::Christmas))
.unwrap_or_default(),
};
(this, skel)
@ -261,6 +266,7 @@ impl Archetype for House {
let roof_color = *self.colors.roof.elim_case_pure(&colors.roof);
let wall_color = *self.colors.wall.elim_case_pure(&colors.wall);
let support_color = *self.colors.support.elim_case_pure(&colors.support);
let christmas_theme = self.christmas_decorations;
let profile = Vec2::new(bound_offset.x, z);
@ -608,12 +614,22 @@ impl Archetype for House {
if dist == width + 1
&& center_offset.map(|e| e.abs()).reduce_min() == 0
&& profile.y == floor_height + 3
&& self
.noise
.chance(Vec3::new(center_offset.x, center_offset.y, z), 0.35)
&& self.noise.chance(
Vec3::new(center_offset.x, center_offset.y, z),
if christmas_theme { 0.70 } else { 0.35 },
)
&& attr.storey_fill.has_lower()
{
let ornament =
let ornament = if christmas_theme {
match self
.noise
.get(Vec3::new(center_offset.x, center_offset.y, z + 100))
% 4
{
0 => SpriteKind::ChristmasWreath,
_ => SpriteKind::ChristmasOrnament,
}
} else {
match self
.noise
.get(Vec3::new(center_offset.x, center_offset.y, z + 100))
@ -624,7 +640,8 @@ impl Archetype for House {
4 => SpriteKind::WallSconce,
5 => SpriteKind::WallLampSmall,
_ => SpriteKind::DungeonWallDecor,
};
}
};
BlockMask::new(
Block::air(ornament).with_ori((edge_ori + 4) % 8).unwrap(),

View File

@ -5,6 +5,7 @@ use crate::{
IndexRef,
};
use common::{
calendar::Calendar,
make_case_elim,
terrain::{Block, BlockKind, SpriteKind},
};
@ -56,7 +57,7 @@ make_case_elim!(
impl Archetype for Keep {
type Attr = Attr;
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>) {
fn generate<R: Rng>(rng: &mut R, _calendar: Option<&Calendar>) -> (Self, Skeleton<Self::Attr>) {
let len = rng.gen_range(-8..24).max(0);
let storeys = rng.gen_range(1..3);
let skel = Skeleton {

View File

@ -3,6 +3,7 @@ pub mod keep;
use super::skeleton::*;
use crate::{site::BlockMask, IndexRef};
use common::calendar::Calendar;
use rand::prelude::*;
use serde::Deserialize;
use vek::*;
@ -16,7 +17,7 @@ pub struct Colors {
pub trait Archetype {
type Attr;
fn generate<R: Rng>(rng: &mut R) -> (Self, Skeleton<Self::Attr>)
fn generate<R: Rng>(rng: &mut R, calendar: Option<&Calendar>) -> (Self, Skeleton<Self::Attr>)
where
Self: Sized;

View File

@ -8,7 +8,7 @@ pub use self::{
};
use crate::IndexRef;
use common::terrain::Block;
use common::{calendar::Calendar, terrain::Block};
use rand::prelude::*;
use serde::Deserialize;
use vek::*;
@ -25,11 +25,11 @@ pub struct Building<A: Archetype> {
}
impl<A: Archetype> Building<A> {
pub fn generate(rng: &mut impl Rng, origin: Vec3<i32>) -> Self
pub fn generate(rng: &mut impl Rng, origin: Vec3<i32>, calendar: Option<&Calendar>) -> Self
where
A: Sized,
{
let (archetype, skel) = A::generate(rng);
let (archetype, skel) = A::generate(rng, calendar);
Self {
skel,
archetype,

View File

@ -440,11 +440,13 @@ impl Settlement {
StructureKind::Keep(Building::<Keep>::generate(
ctx.rng,
Vec3::new(house_pos.x, house_pos.y, alt),
None,
))
} else {
StructureKind::House(Building::<House>::generate(
ctx.rng,
Vec3::new(house_pos.x, house_pos.y, alt),
ctx.sim.map(|sim| sim.calendar.as_ref()).flatten(),
))
},
};

View File

@ -26,7 +26,7 @@ impl Tree {
alt: land.get_alt_approx(origin) as i32,
seed: rng.gen(),
tree: {
let config = TreeConfig::giant(rng, 4.0, false);
let config = TreeConfig::giant(rng, 4.0, true);
ProceduralTree::generate(config, rng)
},
}
@ -60,7 +60,7 @@ impl Tree {
let wpos = wpos2d.with_z(self.alt + z);
let rposf = (wpos - self.origin.with_z(self.alt)).map(|e| e as f32 + 0.5);
let (branch, leaves, _, _) = self.tree.is_branch_or_leaves_at(rposf);
let (branch, leaves, platform, air) = self.tree.is_branch_or_leaves_at(rposf);
if (branch || leaves) && above && col.snow_cover {
canvas.set(
@ -69,7 +69,9 @@ impl Tree {
);
}
let block = if leaves {
let block = if air {
Some(Block::empty())
} else if leaves {
if above && dynamic_rng.gen_bool(0.0005) {
canvas.spawn(
EntityInfo::at(wpos.map(|e| e as f32) + Vec3::unit_z())
@ -96,6 +98,8 @@ impl Tree {
Some(Block::new(BlockKind::Leaves, leaf_col.map(|e| e as u8)))
} else if branch {
Some(Block::new(BlockKind::Wood, Rgb::new(80, 32, 0)))
} else if platform {
Some(Block::new(BlockKind::Wood, Rgb::new(180, 130, 50)))
} else {
None
};

View File

@ -280,6 +280,7 @@ impl Fill {
*seed,
col_sample,
Block::air,
canvas_info.calendar(),
)
}),
Fill::Sampling(f) => f(pos),