diff --git a/Cargo.lock b/Cargo.lock index 4f093f60e2..d6f09b79ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/assets/common/entity/calendar/christmas/aggressive/yeti.ron b/assets/common/entity/calendar/christmas/aggressive/yeti.ron new file mode 100644 index 0000000000..172805f756 --- /dev/null +++ b/assets/common/entity/calendar/christmas/aggressive/yeti.ron @@ -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: [], +) \ No newline at end of file diff --git a/assets/common/items/armor/misc/head/boreal_warhelm.ron b/assets/common/items/armor/misc/head/boreal_warhelm.ron new file mode 100644 index 0000000000..ffc4ab4ab0 --- /dev/null +++ b/assets/common/items/armor/misc/head/boreal_warhelm.ron @@ -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: [], +) diff --git a/assets/common/items/armor/misc/head/hood_dark.ron b/assets/common/items/armor/misc/head/hood_dark.ron index dc73291d00..079bab0468 100644 --- a/assets/common/items/armor/misc/head/hood_dark.ron +++ b/assets/common/items/armor/misc/head/hood_dark.ron @@ -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: [], ) diff --git a/assets/common/items/armor/misc/head/mitre.ron b/assets/common/items/armor/misc/head/mitre.ron index bb98cc4057..ff05f03a7e 100644 --- a/assets/common/items/armor/misc/head/mitre.ron +++ b/assets/common/items/armor/misc/head/mitre.ron @@ -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, diff --git a/assets/common/items/armor/misc/head/spikeguard.ron b/assets/common/items/armor/misc/head/spikeguard.ron index 9ec45cfb2d..144749fc98 100644 --- a/assets/common/items/armor/misc/head/spikeguard.ron +++ b/assets/common/items/armor/misc/head/spikeguard.ron @@ -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: [], ) diff --git a/assets/common/items/armor/misc/head/winged_coronet.ron b/assets/common/items/armor/misc/head/winged_coronet.ron new file mode 100644 index 0000000000..5f0bd68a3b --- /dev/null +++ b/assets/common/items/armor/misc/head/winged_coronet.ron @@ -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: [], +) diff --git a/assets/common/items/calendar/christmas/armor/misc/head/woolly_wintercap.ron b/assets/common/items/calendar/christmas/armor/misc/head/woolly_wintercap.ron new file mode 100644 index 0000000000..23bf565419 --- /dev/null +++ b/assets/common/items/calendar/christmas/armor/misc/head/woolly_wintercap.ron @@ -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: [], +) diff --git a/assets/common/items/food/blue_cheese.ron b/assets/common/items/food/blue_cheese.ron new file mode 100644 index 0000000000..98e8961909 --- /dev/null +++ b/assets/common/items/food/blue_cheese.ron @@ -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], +) \ No newline at end of file diff --git a/assets/common/items/lantern/polaris.ron b/assets/common/items/lantern/polaris.ron new file mode 100644 index 0000000000..11e912ca94 --- /dev/null +++ b/assets/common/items/lantern/polaris.ron @@ -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], +) \ No newline at end of file diff --git a/assets/common/loadout/spots/dwarf_graverobber.ron b/assets/common/loadout/spots/dwarf_graverobber.ron index aab9b883b7..c2ac86c90b 100644 --- a/assets/common/loadout/spots/dwarf_graverobber.ron +++ b/assets/common/loadout/spots/dwarf_graverobber.ron @@ -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([ diff --git a/assets/common/loadout/village/guard.ron b/assets/common/loadout/village/guard.ron index c6d0fceaeb..dde1c8c6fc 100644 --- a/assets/common/loadout/village/guard.ron +++ b/assets/common/loadout/village/guard.ron @@ -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"))), ]), }) diff --git a/assets/common/loadout/village/villager.ron b/assets/common/loadout/village/villager.ron index 9a8f6d02f6..a011391cc1 100644 --- a/assets/common/loadout/village/villager.ron +++ b/assets/common/loadout/village/villager.ron @@ -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), ]), }) diff --git a/assets/common/loot_tables/calendar/christmas/boss.ron b/assets/common/loot_tables/calendar/christmas/boss.ron new file mode 100644 index 0000000000..322d75c1f3 --- /dev/null +++ b/assets/common/loot_tables/calendar/christmas/boss.ron @@ -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")), +] diff --git a/assets/common/loot_tables/calendar/christmas/yeti.ron b/assets/common/loot_tables/calendar/christmas/yeti.ron new file mode 100644 index 0000000000..fe59c11d55 --- /dev/null +++ b/assets/common/loot_tables/calendar/christmas/yeti.ron @@ -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)), +] \ No newline at end of file diff --git a/assets/common/loot_tables/spots/bandit.ron b/assets/common/loot_tables/spots/bandit.ron index 6a80af7ef4..f3abd2a23d 100644 --- a/assets/common/loot_tables/spots/bandit.ron +++ b/assets/common/loot_tables/spots/bandit.ron @@ -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")), diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index 6c7f20e8b2..36ca970343 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -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: [ diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index db22621cb1..819ba3ee12 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -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, diff --git a/assets/voxygen/voxel/armor/misc/head/boreal_warhelm.vox b/assets/voxygen/voxel/armor/misc/head/boreal_warhelm.vox new file mode 100755 index 0000000000..336ebc4d63 --- /dev/null +++ b/assets/voxygen/voxel/armor/misc/head/boreal_warhelm.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f31a092a344dda79bdeb3591f699a5a79e60b802df5497f28e207146cf461966 +size 36456 diff --git a/assets/voxygen/voxel/armor/misc/head/hood_dark.vox b/assets/voxygen/voxel/armor/misc/head/hood_dark.vox old mode 100644 new mode 100755 index 76bf3208ea..384aebad0a --- a/assets/voxygen/voxel/armor/misc/head/hood_dark.vox +++ b/assets/voxygen/voxel/armor/misc/head/hood_dark.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5af2854af8ce6227d350df462aca0b5b9eae9b026b7d2f8cf98d5da43a29e777 -size 10120 +oid sha256:5f6ee2829057bf40594326188b7be6de09bf0526ba8ae1c06fb5dbbd1d6ad023 +size 55399 diff --git a/assets/voxygen/voxel/armor/misc/head/winged_coronet.vox b/assets/voxygen/voxel/armor/misc/head/winged_coronet.vox new file mode 100755 index 0000000000..099fa7bf35 --- /dev/null +++ b/assets/voxygen/voxel/armor/misc/head/winged_coronet.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21dd9207ec110602477e63db55b8e0d14410fe8a6693fc704cfdedcf1066b9ce +size 1616 diff --git a/assets/voxygen/voxel/armor/misc/head/woolly_wintercap.vox b/assets/voxygen/voxel/armor/misc/head/woolly_wintercap.vox new file mode 100755 index 0000000000..c954a7ef04 --- /dev/null +++ b/assets/voxygen/voxel/armor/misc/head/woolly_wintercap.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:772b34ec18ab030657aa7c453ba1ebaf17a782e77db763d1f23c03305ee56e86 +size 32524 diff --git a/assets/voxygen/voxel/humanoid_armor_head_manifest.ron b/assets/voxygen/voxel/humanoid_armor_head_manifest.ron index 5bbc96e032..c4bc6c3aac 100644 --- a/assets/voxygen/voxel/humanoid_armor_head_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_head_manifest.ron @@ -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 + ), } )) diff --git a/assets/voxygen/voxel/humanoid_lantern_manifest.ron b/assets/voxygen/voxel/humanoid_lantern_manifest.ron index 20b668aaca..5011cacc3d 100644 --- a/assets/voxygen/voxel/humanoid_lantern_manifest.ron +++ b/assets/voxygen/voxel/humanoid_lantern_manifest.ron @@ -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 + ), }, )) diff --git a/assets/voxygen/voxel/lantern/polaris.vox b/assets/voxygen/voxel/lantern/polaris.vox new file mode 100644 index 0000000000..09c87f0223 --- /dev/null +++ b/assets/voxygen/voxel/lantern/polaris.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23d148804ec91fec5851c8239cdabb0240ceacfe56b7f84066aa3043d85ea876 +size 1308 diff --git a/assets/voxygen/voxel/object/blue_cheese.vox b/assets/voxygen/voxel/object/blue_cheese.vox new file mode 100644 index 0000000000..2aff792ac0 --- /dev/null +++ b/assets/voxygen/voxel/object/blue_cheese.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f09d9368ab046737a6ed634f03823c258be76be536833c7633cdf8284a3d7732 +size 3460 diff --git a/assets/voxygen/voxel/sprite/furniture/moravian-star-orange.vox b/assets/voxygen/voxel/sprite/furniture/moravian-star-orange.vox new file mode 100644 index 0000000000..6819d5773a --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/moravian-star-orange.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc4d1e4e37cb52e03ceaa57fb18ee19a1208efaf94e54876e02937ad99dd2b7a +size 23100 diff --git a/assets/voxygen/voxel/sprite/furniture/snowflake_light.vox b/assets/voxygen/voxel/sprite/furniture/snowflake_light.vox new file mode 100644 index 0000000000..42fd877ec8 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/snowflake_light.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1c46e38a9a979f78b481a0d783c7f0cf0a4e1d04bc216854b10afa461c3ee0a +size 22875 diff --git a/assets/voxygen/voxel/sprite/furniture/wreath-0.vox b/assets/voxygen/voxel/sprite/furniture/wreath-0.vox new file mode 100644 index 0000000000..5c9156bfc5 --- /dev/null +++ b/assets/voxygen/voxel/sprite/furniture/wreath-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a26e26bbef0fc840241221829d7521a9e12cbcfbce687ad7fe7edd12e3fa0e62 +size 2004 diff --git a/assets/voxygen/voxel/sprite_manifest.ron b/assets/voxygen/voxel/sprite_manifest.ron index f674af54c6..8023df010a 100644 --- a/assets/voxygen/voxel/sprite_manifest.ron +++ b/assets/voxygen/voxel/sprite_manifest.ron @@ -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: [ diff --git a/assets/world/wildlife/spawn/calendar/christmas/taiga/core_forest.ron b/assets/world/wildlife/spawn/calendar/christmas/taiga/core_forest.ron new file mode 100644 index 0000000000..0ef64cad9a --- /dev/null +++ b/assets/world/wildlife/spawn/calendar/christmas/taiga/core_forest.ron @@ -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], + ), + ], +) \ No newline at end of file diff --git a/assets/world/wildlife/spawn/calendar/christmas/tundra/core.ron b/assets/world/wildlife/spawn/calendar/christmas/tundra/core.ron new file mode 100644 index 0000000000..f3e5d9f189 --- /dev/null +++ b/assets/world/wildlife/spawn/calendar/christmas/tundra/core.ron @@ -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], + ), + ], +) \ No newline at end of file diff --git a/assets/world/wildlife/spawn/calendar/christmas/tundra/forest.ron b/assets/world/wildlife/spawn/calendar/christmas/tundra/forest.ron new file mode 100644 index 0000000000..d924f6d4b2 --- /dev/null +++ b/assets/world/wildlife/spawn/calendar/christmas/tundra/forest.ron @@ -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], + ), + ], +) \ No newline at end of file diff --git a/assets/world/wildlife/spawn/calendar/christmas/tundra/snow.ron b/assets/world/wildlife/spawn/calendar/christmas/tundra/snow.ron new file mode 100644 index 0000000000..4e873b7b4b --- /dev/null +++ b/assets/world/wildlife/spawn/calendar/christmas/tundra/snow.ron @@ -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], + ), + ], +) \ No newline at end of file diff --git a/client/src/lib.rs b/client/src/lib.rs index 4a3524dca2..afd41d5245 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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 diff --git a/common/Cargo.toml b/common/Cargo.toml index 911ab271f1..f935d53c0e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -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"] } diff --git a/common/net/src/msg/compression.rs b/common/net/src/msg/compression.rs index 7d02c95efb..53fa1c3c21 100644 --- a/common/net/src/msg/compression.rs +++ b/common/net/src/msg/compression.rs @@ -621,6 +621,11 @@ impl VoxelImageDecoding for TriPngEncoding Rgb { + r: 150, + g: 190, + b: 255, + }, Earth => Rgb { r: 200, g: 140, diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 69c1d5358c..3238762f41 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -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), CreateEntity(sync::EntityPackage), @@ -305,7 +306,7 @@ impl ServerMsg { | ServerGeneral::ChatMsg(_) | ServerGeneral::ChatMode(_) | ServerGeneral::SetPlayerEntity(_) - | ServerGeneral::TimeOfDay(_) + | ServerGeneral::TimeOfDay(_, _) | ServerGeneral::EntitySync(_) | ServerGeneral::CompSync(_) | ServerGeneral::CreateEntity(_) diff --git a/common/src/calendar.rs b/common/src/calendar.rs new file mode 100644 index 0000000000..f9934d252a --- /dev/null +++ b/common/src/calendar.rs @@ -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, +} + +impl Calendar { + pub fn is_event(&self, event: CalendarEvent) -> bool { self.events.contains(&event) } + + pub fn events(&self) -> impl ExactSizeIterator + '_ { + self.events.iter() + } + + pub fn from_events(events: Vec) -> Self { Self { events } } + + pub fn from_tz(tz: Option) -> Self { + let mut this = Self::default(); + + let now = match tz { + Some(tz) => { + let utc = Utc::now().naive_utc(); + DateTime::::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 + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 58acc8ed53..e6109bb5c4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -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; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 710b0e353c..c567d46769 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -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)) diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 797d3a3b9f..ef8f2ade62 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -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 { 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 } diff --git a/common/src/terrain/map.rs b/common/src/terrain/map.rs index 887cd89990..5fb3101e46 100644 --- a/common/src/terrain/map.rs +++ b/common/src/terrain/map.rs @@ -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); diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index e174f7ffd4..e6fdd6c28a 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -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 ) } } diff --git a/common/state/src/state.rs b/common/state/src/state.rs index a0ac7034e6..433f95df49 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -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)); diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index c48894fccc..cbd26a0019 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1692,8 +1692,18 @@ fn box_voxel_collision<'a, T: BaseVol + 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; } diff --git a/server/Cargo.toml b/server/Cargo.toml index 13a1a4a66c..85be372d3a 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -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" diff --git a/server/src/chunk_generator.rs b/server/src/chunk_generator.rs index f8655eff0c..45c0ecae79 100644 --- a/server/src/chunk_generator.rs +++ b/server/src/chunk_generator.rs @@ -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, index: IndexOwned, - time: TimeOfDay, + time: (TimeOfDay, Calendar), ) { let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) { v diff --git a/server/src/client.rs b/server/src/client.rs index 409f619f0e..ca69bb0421 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -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(_) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 7b93f61a61..cf88f0632d 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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::(); + let calendar = server.state.ecs().read_resource::(); 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::()).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 { diff --git a/server/src/lib.rs b/server/src/lib.rs index e6b76c6132..371a28db3b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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::().0 += 1; self.state.ecs().write_resource::().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::() + .calendar_mode + .calendar_now(); + *self.state.ecs_mut().write_resource::() = 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::(), + ( + *ecs.read_resource::(), + (*ecs.read_resource::()).clone(), + ), ); }); } @@ -1104,7 +1120,10 @@ impl Server { &slow_jobs, Arc::clone(&self.world), self.index.clone(), - *ecs.read_resource::(), + ( + *ecs.read_resource::(), + (*ecs.read_resource::()).clone(), + ), ); } diff --git a/server/src/settings.rs b/server/src/settings.rs index d3cd0ace6c..d23fd7698d 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -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), +} + +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, pub safe_spawn: bool, pub max_player_for_kill_broadcast: Option, + 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, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 138ab86323..05431bb41c 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -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::()); + let time = (*ecs.read_resource::(), (*ecs.read_resource::()).clone()); + chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), index.clone(), time); } }); } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 964df88283..ad6742dcce 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -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); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 868ed6270b..4f3f4d0b35 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -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>, @@ -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()), ) }); diff --git a/voxygen/benches/meshing_benchmark.rs b/voxygen/benches/meshing_benchmark.rs index 9ebff52ec2..a5345b960e 100644 --- a/voxygen/benches/meshing_benchmark.rs +++ b/voxygen/benches/meshing_benchmark.rs @@ -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, ); diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 132f34e17e..afb0ceebf6 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -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), diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 8077d69462..0e2d1bfdb7 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -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); diff --git a/world/examples/chunk_compression_benchmarks.rs b/world/examples/chunk_compression_benchmarks.rs index 7d5698c41b..28e37d0c16 100644 --- a/world/examples/chunk_compression_benchmarks.rs +++ b/world/examples/chunk_compression_benchmarks.rs @@ -677,6 +677,7 @@ fn main() { WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + calendar: None, }, &pool, ); diff --git a/world/examples/dungeon_voxel_export.rs b/world/examples/dungeon_voxel_export.rs index 307f9bdbb1..b9308454b1 100644 --- a/world/examples/dungeon_voxel_export.rs +++ b/world/examples/dungeon_voxel_export.rs @@ -29,6 +29,7 @@ fn main() -> Result { WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + calendar: None, }, &pool, ); diff --git a/world/examples/heightmap_visualization.rs b/world/examples/heightmap_visualization.rs index 27b99708a2..12403b3313 100644 --- a/world/examples/heightmap_visualization.rs +++ b/world/examples/heightmap_visualization.rs @@ -124,6 +124,7 @@ fn main() { WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + calendar: None, }, &pool, ); diff --git a/world/examples/pricing_csv.rs b/world/examples/pricing_csv.rs index 0565e4a99e..517d62cd1b 100644 --- a/world/examples/pricing_csv.rs +++ b/world/examples/pricing_csv.rs @@ -166,6 +166,7 @@ fn main() { WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + calendar: None, }, &pool, ); diff --git a/world/examples/view.rs b/world/examples/view.rs index 56f0b105ac..07e7d0f747 100644 --- a/world/examples/view.rs +++ b/world/examples/view.rs @@ -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, diff --git a/world/examples/water.rs b/world/examples/water.rs index c25451bf90..13d8e8b6f8 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -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::>() @@ -177,6 +179,7 @@ fn main() { is_basement, is_water, + is_ice: true, is_shaded, is_temperature, is_humidity, diff --git a/world/examples/world_block_statistics.rs b/world/examples/world_block_statistics.rs index cccfd75782..c83f20ef73 100644 --- a/world/examples/world_block_statistics.rs +++ b/world/examples/world_block_statistics.rs @@ -67,6 +67,7 @@ fn generate(db_path: &str, ymin: Option, ymax: Option) -> Result<(), B WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + calendar: None, }, &pool, ); diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 8c3c684c25..5f2ae832ca 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -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>>, wpos: Vec2, 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, index: IndexRef<'a>) -> Option> { + pub fn get_z_cache( + &mut self, + wpos: Vec2, + index: IndexRef<'a>, + calendar: Option<&'a Calendar>, + ) -> Option> { 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, z_cache: Option<&ZCache>) -> Option { @@ -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 { let field = RandomField::new(structure_seed); @@ -312,15 +329,21 @@ pub fn block_from_structure( }; range.map(|range| { - Block::new( - BlockKind::Leaves, - Rgb::::lerp( - Rgb::::from(range.start).map(f32::from), - Rgb::::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::::lerp( + Rgb::::from(range.start).map(f32::from), + Rgb::::from(range.end).map(f32::from), + lerp, + ) + .map(|e| e as u8), ) - .map(|e| e as u8), - ) + } }) }, StructureBlock::BirchWood => { diff --git a/world/src/canvas.rs b/world/src/canvas.rs index 30fc4b424a..78b0f0c46b 100644 --- a/world/src/canvas.rs +++ b/world/src/canvas.rs @@ -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 { self.wpos } pub fn area(&self) -> Aabr { @@ -51,9 +55,11 @@ impl<'a> CanvasInfo<'a> { /// This function does not (currently) cache generated columns. pub fn col_or_gen(&self, wpos: Vec2) -> Option> { 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 { diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index e4b860edc5..42f3871523 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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, IndexRef<'a>); + type Index = (Vec2, IndexRef<'a>, Option<&'a Calendar>); type Sample = Option>; - fn get(&self, (wpos, index): Self::Index) -> Option> { + fn get(&self, (wpos, index, calendar): Self::Index) -> Option> { 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, + pub ice_depth: f32, pub chunk: &'a SimChunk, } diff --git a/world/src/config.rs b/world/src/config.rs index ce9a27c65b..6945b043f7 100644 --- a/world/src/config.rs +++ b/world/src/config.rs @@ -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, } 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)] diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index b6412dbec0..1968191c9a 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -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::().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)), } } diff --git a/world/src/layer/wildlife.rs b/world/src/layer/wildlife.rs index 72eca50d5d..abd3c9001c 100644 --- a/world/src/layer/wildlife.rs +++ b/world/src/layer/wildlife.rs @@ -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 { + pub fn request( + &self, + requested_period: DayPeriod, + calendar: Option<&Calendar>, + underwater: bool, + ) -> Option { 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, + #[serde(default)] + pub calendar_events: Option>, /* 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, + 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::() < density * col_sample.spawn_rate && col_sample.gradient < Some(1.3)) diff --git a/world/src/lib.rs b/world/src/lib.rs index 4baad5f24f..8ea10f135f 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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, IndexRef), Sample = Option> + '_ { + ) -> impl Sampler< + Index = (Vec2, IndexRef, Option<&'_ Calendar>), + Sample = Option, + > + '_ { ColumnGen::new(&self.sim) } @@ -215,8 +219,10 @@ impl World { chunk_pos: Vec2, // TODO: misleading name mut should_continue: impl FnMut() -> bool, - time: Option, + 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 diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index f2c3b8d3a4..77b3d79821 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -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) diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 1aba30c493..edbc4330b4 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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, } 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, } 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); diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index 02a133201c..f7d7bd5cf1 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -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"); diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index 5cf7220b9b..60672d2332 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -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(rng: &mut R) -> (Self, Skeleton) { + fn generate(rng: &mut R, calendar: Option<&Calendar>) -> (Self, Skeleton) { 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(), diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index cd68d8662f..065d34dd53 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -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(rng: &mut R) -> (Self, Skeleton) { + fn generate(rng: &mut R, _calendar: Option<&Calendar>) -> (Self, Skeleton) { let len = rng.gen_range(-8..24).max(0); let storeys = rng.gen_range(1..3); let skel = Skeleton { diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs index 169b8e06ed..e09a59205f 100644 --- a/world/src/site/settlement/building/archetype/mod.rs +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -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(rng: &mut R) -> (Self, Skeleton) + fn generate(rng: &mut R, calendar: Option<&Calendar>) -> (Self, Skeleton) where Self: Sized; diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs index 5b7336e0f7..4f0a290957 100644 --- a/world/src/site/settlement/building/mod.rs +++ b/world/src/site/settlement/building/mod.rs @@ -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 { } impl Building { - pub fn generate(rng: &mut impl Rng, origin: Vec3) -> Self + pub fn generate(rng: &mut impl Rng, origin: Vec3, calendar: Option<&Calendar>) -> Self where A: Sized, { - let (archetype, skel) = A::generate(rng); + let (archetype, skel) = A::generate(rng, calendar); Self { skel, archetype, diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 6a0a7a8b52..66fca1d733 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -440,11 +440,13 @@ impl Settlement { StructureKind::Keep(Building::::generate( ctx.rng, Vec3::new(house_pos.x, house_pos.y, alt), + None, )) } else { StructureKind::House(Building::::generate( ctx.rng, Vec3::new(house_pos.x, house_pos.y, alt), + ctx.sim.map(|sim| sim.calendar.as_ref()).flatten(), )) }, }; diff --git a/world/src/site/tree.rs b/world/src/site/tree.rs index 9852b734ae..5446648519 100644 --- a/world/src/site/tree.rs +++ b/world/src/site/tree.rs @@ -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 }; diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index 142d2c31d3..a59d895873 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -280,6 +280,7 @@ impl Fill { *seed, col_sample, Block::air, + canvas_info.calendar(), ) }), Fill::Sampling(f) => f(pos),