oriverse-engine 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AGENT_ONBOARDING.md +74 -74
- package/dist/CODEX_PROMPT.md +9 -9
- package/dist/examples/coin_collector.weave +70 -70
- package/dist/examples/dodge_blocks.weave +70 -70
- package/dist/examples/target_range.weave +82 -82
- package/dist/ktx_bridge.js +121 -121
- package/dist/ktx_lib.js +1 -1
- package/dist/ktx_lib.wasm +0 -0
- package/dist/oriverse_wgpu.d.ts +80 -45
- package/dist/oriverse_wgpu.js +344 -112
- package/dist/oriverse_wgpu_bg.wasm +0 -0
- package/dist/oriverse_wgpu_bg.wasm.d.ts +50 -44
- package/dist/prewarm.js +11 -0
- package/dist/shader_prewarm_manifest.json +1 -1
- package/dist/snippets/oriverse_market_client-0cd3013288f3c49c/inline1.js +41 -0
- package/dist/snippets/oriverse_wgpu-255445ce6d9a0851/inline0.js +26 -58
- package/dist/snippets/oriverse_wgpu-255445ce6d9a0851/inline1.js +58 -20
- package/dist/starter/AGENTS.md +2 -2
- package/dist/starter/CLAUDE.md +2 -2
- package/dist/starter/game.html +8 -8
- package/dist/templates/game.html +2 -2
- package/dist/templates/game_cdn.html +8 -8
- package/dist/weave_documentation.txt +2886 -175
- package/package.json +1 -1
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
|
|
2
2
|
>>>>>>>>>>>>
|
|
3
3
|
Weave World Description Language Documentation
|
|
4
|
-
Version:
|
|
4
|
+
Version:19Aug26-09:37-59549
|
|
5
5
|
>>>>>>>>>>>>
|
|
6
6
|
|
|
7
7
|
<weave_language>
|
|
8
8
|
- Weave is used to code on Oriverse (syntax similar to Swift for logic, plus scene declarations at top level).
|
|
9
9
|
- A Weave file has two kinds of top-level content:
|
|
10
|
-
- scene declarations: `object`, `saved`, `physics`, `crowd`, `display`, `region` — describe what exists in the world.
|
|
10
|
+
- scene declarations: `object`, `saved`, `physics`, `crowd`, `display`, `region`, and lights `pointLight`/`spotLight`/`areaLight` — describe what exists in the world.
|
|
11
11
|
- class code: `#class <Name> ... #end` blocks of logic; each block compiles to bytecode attached to a game object.
|
|
12
|
-
- Compiler directives (top-level only): #class, #shader (body
|
|
12
|
+
- Compiler directives (top-level only): #class, #shader (custom WGSL on the engine lit material — lighting/shadows/IBL stay engine-owned; `@vertex { }` mutates ctx.world_pos for wind/sway, fragment writes out.base_color/world_normal/orm; bind via `material M { shader = "S" }`; live knobs via SetMaterialParameterNumber; body authoring in the shader_dsl skill), #texture (CPU-baked procedural PBR texture; auto-creates `material <Name>` usable as `material = "<Name>"`; defaults to triplanar/repeat/opaque, supports `mapping = uv;`, `wrap = clamp;`, `render_mode = cutout;`, and scalar `out.alpha`; see <texture_dsl_usage>; body authoring in the texture_dsl skill), #mesh (local CPU-baked procedural static mesh model `mesh.<Name>`; see <mesh_dsl_usage>; body authoring in the mesh_dsl skill), #tree (procedural tree species `tree.<Name>`; see <tree_dsl_usage>), #forest (scattered-tree region `forest.<Name>`; see <forest_dsl_usage>), #scatter (scattered-model region `scatter.<Name>`, display-only, no collision; see <scatter_dsl_usage>), #particles (local particle effect JSON asset `effect.<Name>`; `particleTextureName` takes `"flipbook.<word>"` for a shared-library animated atlas, `"texture.<Texture>.color"` for a local #texture, or `"image.<User>.<Name>"` for a known ori image asset; see particle_vfx skill), #sound (local CPU-baked procedural sound asset `sound.<name>`; see <sound_dsl_usage>), #adjuster (user-tunable look knobs; see <adjuster_usage>), #rules (see <world_rules>), #end, #set <Name> = <Number> (TileSize, Realistic; texture policy, display-only: `#set TextureMaxResolution 1024` caps every market texture this world downloads to <=1024px [picks the market's smaller tier], `#set TextureFullResolutionAssets "model.a.x, model.b.y"` always loads listed assets at full resolution, `#set TextureFullResolutionUpgrade 1` enables the desktop post-load full-res texture re-fetch [default off: the world keeps its load-time texture tier — project-load assets land quarter-res; wasm/mobile never run the upgrade]), #doc_version <N> (authoring doc version; see <updating_old_worlds>).
|
|
13
13
|
- Scene numeric fields support constant arithmetic: `+`, `-`, `*`, `/`, unary sign, parentheses.
|
|
14
14
|
- One statement per line; separate statements on the same line with `;`. An empty list literal `[]` needs a List-typed context, e.g. `var H: List<Number> = []`.
|
|
15
15
|
- Numbers are deterministic 48.16 fixed-point; tiny fractions lose precision — track small quantities in a smaller unit (e.g. hundredths of a cm as integers).
|
|
16
16
|
- No recursion: direct or mutual func call cycles are a compile error; rewrite as loops (a List can serve as an explicit stack).
|
|
17
17
|
- Scene object declarations can set position, rotation, size, height, model; model is an Oriverse URL like `ori.username.modelname`; attach a class with `:` (e.g. `object Foo : MyClass { ... }`).
|
|
18
18
|
- Default mesh-backed `object`/`physics`/`crowd`/`display`: `position=(x,y,0)` means bottom-center pivot on ground Z=0. Effects/lights/regions/terrain/groups/canvases use center placement.
|
|
19
|
-
-
|
|
20
|
-
-
|
|
19
|
+
- Dynamic lights: `pointLight Lamp { position=(x,y,z) radius=600 intensity=5 color=#ffd9a0 }`; `spotLight` shines along local +X (aim with rotation), `areaLight` emits from its `size` rectangle. `radius` = falloff range in cm; `size` does NOT change a point/spot light (editor gizmo only); `cast_shadow = true` on point/spot only. Nest in `saved` templates to spawn lights from code; for lamps built into a model prefer the mesh `emitter(...)` socket.
|
|
20
|
+
- For axis-aligned cube walls/floors/platforms, prefer `bounds_min=(x1,y1,z1)` + `bounds_max=(x2,y2,z2)` over `position`+`size`; it names exact world-space box corners and avoids center/half-size mistakes. Example: `object NorthWall { model="cube" bounds_min=(-1500,1100,0) bounds_max=(1500,1180,280) }`. Cube boxes only (`model="cube"`; `region_cube` for regions) — other models must use position+size. Do not combine with position/rotation/size/height/tile fields.
|
|
21
|
+
- Object size clamps per axis at load: 500m static-collidable, 100m movable-physics, 1km with collision off — tile bigger floors/walls from 500m pieces.
|
|
22
|
+
- Grass on objects: static mesh `object`/`display` clauses accept grass = true (blades on upward faces; steep faces stay bare; display-only, no collision; global SetGrass* look knobs apply; skinned models grow nothing). On model = "cube" blades cover the top face (yaw rotation only) and grass_border = round|noise (edge falloff, default full face) + grass_border_noise_scale = 220 (cm, noise feature size) shape the edge.
|
|
21
23
|
- Prefer `height = ...` for imported/custom models to preserve proportions; use `ScaleToHeight(...)` at runtime; use `size = ...` only when intentionally stretching X/Y/Z.
|
|
22
24
|
- "world description" = the serialized text of a world (scene + classes); saved from the editor and pasted back to reload. AI builds these; users paste them in.
|
|
23
25
|
- `#class player` is auto-attached to every PlayerObject. Do not declare the player in scene. Declaring it REPLACES the built-in default player: call SetDefaultPlayerControlsEnabled(true) in its OnSpawned to keep built-in WASD move + jump, or code a movement loop — with neither, players cannot move (see <camera>).
|
|
@@ -34,7 +36,7 @@ Weave World Description Language Documentation
|
|
|
34
36
|
- Name aliases by TYPE: `alias.<type>.<name>` with <type> in {model, effect, image, sound}, e.g. `model.goblin`, `effect.explosion`, `image.gold_icon`, `sound.hit`. A typed alias must resolve to a matching asset type.
|
|
35
37
|
- You usually do NOT know real ori urls. Prefer typed alias placeholders and let the system/human bind real assets later:
|
|
36
38
|
- All four types may be referenced UNDECLARED or declared bare (`alias sound.hit`, no `=` target); they resolve from the generated libraries below, show a safe placeholder until generation lands (cube / default explosion / placeholder sprite / placeholder clip), and a human can rebind them.
|
|
37
|
-
- MODEL aliases resolve from a generated-mesh library: a bare declaration `alias model.tree` (no `=` target, auto-added for undeclared refs too) binds a matching generated mesh by name at load; on a miss it shows a cube while a mesh is generated in the background
|
|
39
|
+
- MODEL aliases resolve from a generated-mesh library: a bare declaration `alias model.tree` (no `=` target, auto-added for undeclared refs too) binds a matching generated mesh by name at load; on a miss it shows a placeholder (cube; the player a mannequin stand-in) while a mesh is generated in the background, swapping in live when it lands. Name model aliases with plain singular nouns (`model.tree`, `model.market_stall`, not `model.trees`/`model.bigTreeA`) so library matching works. Want a specific existing mesh to start from or adapt -> the `mesh_library_search` tool returns library `#mesh` source to inline. A bound word's source is already in the world text (`#mesh gen_<word>` + its `material`/`#texture` clauses) — recolor/restyle by editing those directly. Character-noun words mint as rigged humanoids that auto-play idle/walk/run; the alias status marks minted words `rigged humanoid`.
|
|
38
40
|
- IMAGE aliases resolve the same way from a generated-UI-image library: bare `alias image.gold_icon` binds a generated icon by name at load; on a miss it shows a placeholder sprite while an icon is generated in the background. Prefer bare image aliases for UI icons; name them by subject (`image.gold_icon`, `image.energy_icon`).
|
|
39
41
|
- SOUND aliases resolve the same way from a generated-sound library: bare `alias sound.coin_pickup` binds a generated clip by name at load; on a miss it plays a placeholder clip while a sound is generated in the background. Name sound aliases by the event they accompany (`sound.coin_pickup`, `sound.sword_hit`, `sound.door_creak`) so generation prompts come out right.
|
|
40
42
|
- ANIMATION: referencing an undefined `Ori.anim.canon.<verb>` appends the canonical-clip library's `#clip <verb> on canon` block at load (plain action verbs: `anim.canon.cheer`, `anim.canon.stomp`); it plays on any rigged humanoid via engine retarget. On a library miss the character keeps its current animation; the verb is queued for house minting and binds on a later load. A world-authored `#clip <verb> on canon` always wins.
|
|
@@ -46,58 +48,62 @@ Weave World Description Language Documentation
|
|
|
46
48
|
</asset_aliases>
|
|
47
49
|
|
|
48
50
|
<asset_word_library>
|
|
49
|
-
- Established
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
- Established library words per alias namespace, plus `flipbook.<word>` particle textures. PREFER an existing word when one matches the need — it binds instantly with no generation wait or spend. Other words still work: misses show a placeholder and auto-generate in the background (flipbook words are minted deliberately via the flipbook_gen tool instead).
|
|
52
|
+
model: abandoned_car alchemist alien_bug alien_hive alley_monster amphora anchor aqua_penguin assembler athlete aunt auntie axe ball banana banana_peel bandage bandit barn barracks barrel barrel_2 base_core baseball_bat battle_car bed bee beehive belt bench bench_2 berry_bush billboard blacksmith boar boat boat_parts bomb bone book boomerang boulder boulder_2 box bread_slice bread_slice_2 brick bridge buddhist_temple bush bush_2 bush_a butter cabbage camp_tent camp_tent_2 campfire canvas_tent cargo_truck carrot cart carved_owl_statue cash_bundle cat cat_blacksmith cat_cart_keeper cat_chef cat_engineer cat_factory_worker cat_merchant cat_rickshaw chair chair_2 checkpoint cheese cheese_2 chest chicken chicken_coop chime_lantern chunk_floor circular_saw clay_buddy cleric cloud clue_item clue_item_2 coin command_center cottage cousin cow crate crate_2 crop crop_plot crossbow crystal crystal_cluster crystal_shard cup customer customer_2 cutting_board cutting_board_2 cyclops dog dragon drill druid dual_blades durian elegant_woman ember_fox enemy_spawn energy_drink evidence_item exp_gem fan farmhouse farmhouse_floor fence finish_arch fish fish_2 fishing_rod flag flag_2 flamethrower flashlight flat_rock_slab flower food_crate forager_hut foundation fountain fox fridge fridge_2 frog frying_pan frying_pan_2 fuel_pump furniture_bookshelf furniture_table furniture_table_2 furniture_wardrobe gargoyle gate ghoul ghoul_2 gilded_hourglass glimmer_crate goblin godfather golden_beak_shaman golden_coin grandma grandpa grape grape_2 grass_rock grass_rock_cliff great_sword green_maple_tree greenhouse grocery_box ground_item guard guard_tower hammer hamster harpy hay_bale hay_bale_2 heal_pickup held_item horned_beast horse hospital_assistant house hungry_mouse hunter_bow hunting_hammer hut ice_shard idle_character invoker_mage iron_node item_crate jam_jar kid killer knife knight ladder lamp lamp_2 lane lantern lantern_2 light_pole log_pile log_pile_2 lumber_camp machine_gun mage magnet market_stall mecha_chameleon medic medicine_crate miner minotaur moss_boar mushroom nephew niece night_watchman ninja_runner npc nurse oak_tree officer owl paddle palm_tree patient pearl_bush pebble person pickaxe pickup_orb pig pine_tree pistol plate plate_2 player_char police_car pond poop projectile pumpkin rabbit rabbit_runner ragdoll_fighter rain_mill rain_totem ranger requiem_gun resident revolver rifle road_sign robot_grunt robot_runner rock rocket rocket_launcher rocksample rope rope_bundle runic_obelisk sandbag sea_mine sedan sentinel_drone shark shield shipping_container shop_counter shop_floor shopkeeper shotgun shuriken signpost signpost_2 signpost_3 skull slime small_rock smg smoke_bomb snail sniper_rifle soil_tile soldier spider sports_car squid stair star star_core statue stepmother stilt_house stone_pile stonecutter_lodge storehouse storm_hearth storm_shrine surgeon survivor sword table tank_hull tent timber_bundle toaster toaster_2 tool_chest torch torch_2 tower training_dummy training_dummy_2 travel_pack tree tribal_cat_elder tribal_cat_villager trophy trunk turret uncle villager volleyball wagon wall warehouse watchtower water_barrel water_well watercan watering_can weapon_rack weapon_rack_2 weapon_shop_sign well wheat wheelbarrow white_tribal_cat_player wizard_staff wood_fence wood_fence_2 wooden_stool worker_robot zebrite_totem zombie
|
|
53
|
+
image: airtank_icon alien_bug angled_player_banner axe_icon bandage_icon bean_icon bean_seeds_icon berries_icon book_icon boss_skull bread_icon bullet_icon cabbage_icon campfire_icon carrot_icon cheese_icon clock_icon coin_icon crop_icon crossed_swords crossed_swords_2 crosshair cute_paw_cursor dash_icon deck_icon deck_icon_2 energy_icon fish_icon fishing_rod_icon flamethrower_icon flint_icon fluffy_sheep_party_portrait forest_ancient_tree_spirit_wide_splash forest_ent gold_coin_icon gold_icon gold_icon_2 hand_icon health_icon health_icon_2 hoe_icon horse_party_portrait hunger_icon inverse_trapezoid_top_bar iron_ore_icon knight_helmet loading_splash log_icon machine_gun_icon mackerel_icon monster_head moon_icon orange_cat_party_portrait panel_frame parsnip_icon parsnip_seeds_icon pickaxe_icon poison_icon potato_icon potato_seeds_icon purple_hive_mind_insect_warrior_wide_splash qa_probe_gem radish_icon revolver_icon rifle_icon robot_face robot_vending_mech_champion_wide_splash rocket_launcher_icon round_table_knight_wide_splash ruby_gem_icon salvage_icon sanity_icon sardine_icon scythe_icon seed_bag_icon seed_icon shotgun_icon skill_dagger_icon skill_fireaura_icon skill_hammer_icon skill_shield_icon skill_stealth_icon snapper_icon snowflake_spin squid_icon steak_icon stone_icon sun_icon tft_alien_portrait_black tft_boss_skull_black tft_clock_black tft_crossed_swords_black tft_forest_portrait_black tft_monster_head_black tft_robot_portrait_black tft_yokai_portrait_black torch_icon tuna_icon twigs_icon undead_death_knight_wide_splash undead_skull water_drop_icon watering_can_icon white_chicken_party_portrait wood_icon yokai_mask yokai_samurai_oni_wide_splash
|
|
54
|
+
sound: ambient_horror apartment_ambience arena_warning baby_raptor_step banana_boomerang baton_hit baton_swing bear_shotgun bite blast body_thump body_thump_2 boost boost_burst bow_hit branch_snap brass_gong cannon_blast cannon_fire car_horn card_draw card_play cash_pickup cash_pickup_2 character_select checkpoint chomp coin_pickup coin_pickup_2 corn_burst countdown countdown_beep countdown_tick crab_blaster crab_hit crab_skill crash crash_burst critical_impact critical_impact_2 croc_machinegun crow_call crowd_cheer crystal_pickup dash_burst dodge dodge_dust dodge_dust_2 dog_bark dog_pistol door_close door_open door_open_2 door_unlock dual_hit earthquake_rumble earthquake_rumble_2 electric_burst electric_shock eliminated energy_empty engine_acceleration engine_idle exit_unlock explosion finish finish_chime fish_caught fishing_cast fishing_reel flamethrower_burst food_complete food_complete_2 front_weapon_activation game_check game_end game_start gameplay_music gameplay_music_2 glimmer_bell gold_pickup golf_cup golf_swing grenade_launcher gunshot hammer_hit hammer_hit_2 heartbeat hit hook_impact hook_impact_2 hook_throw hook_throw_2 item_pickup item_pickup_2 laser_shot laser_shot_2 lasgun_shot level_up level_up_2 level_up_3 level_up_4 level_up_5 line_clear machine_gun machine_gun_reload machine_gun_shot magic_blast medkit_pickup medkit_use menu_music metal_hit metal_hit_2 mine missile_launch mole_sniper monster_attack monster_attack_2 monster_roar monster_roar_2 owl_hoot pickup piece_capture piece_lock piece_move pistol_reload pistol_shot place player_hit player_hurt player_hurt_2 police_siren punch rain_ambience raptor_step ready_up rear_ability_activation repair resource_pickup respawn revive rifle_reload rifle_shot rifle_shot_2 ring_out ringout robot_beam robot_blast robot_charge robot_laser rock_riff rocket_shot rot_loop royal_capture score_boost shield shield_block ship_destroyed shotgun_shot signal_ping siren slab_turn slap slap_hit stone_rotate survivor_step sword_clang sword_clang_2 sword_hit sword_hit_2 tire_skid toast_pop toy_pop trex_step turn_alert ui_click ui_click_2 ui_click_3 ui_click_4 ui_click_5 vehicle_damaged vehicle_destroyed victory victory_fanfare victory_fanfare_2 victory_sting victory_sting_2 vortex_hum wall_deploy wall_pickup warning_siren water_splash wave_crash weapon_switch whistle wind_gust wind_gust_2 wolf_howl wood_impact zebrite_chime zombie_attack zombie_growl zombie_roar
|
|
55
|
+
effect: acid_splash arrow_hit bite_splash blood_hit blood_splash boost_burst cannon_blast capture_burst cartoon_impact cash_sparkle cash_sparkle_2 coin_sparkle collapse_dust crash_burst critical_smoke damage_smoke damage_sparks damage_sparks_2 damage_sparks_3 dash_trail dash_trail_2 dismember dodge_dust dust_puff dust_puff_2 dust_puff_3 electric_burst eliminated_burst explosion explosion_burst finish_confetti fire_burst fireball fireball_burst gold_rush golf_cup golf_hit grenade_blast hammer_impact heal_glow healing_glow hit_burst hook_hit hook_hit_2 impact_flash_critical impact_flash_small impact_shockwave impact_shockwave_2 impact_shockwave_3 impact_sparks ink_cloud item_glow magic_blast magic_sparkle magic_vortex metal_hit meteor_fire mine_burst muzzle_flash objective_pickup player_hit poison_cloud rage_burst rainbow_burst rainbow_burst_2 rapid_slash respawn_burst revive_burst revive_glow rickshaw_burn ringout_explosion rocket_explosion rot_pulse rune_pickup shield_burst shield_pop slap_hit slash_burst slash_burst_2 slash_burst_3 smoke_puff smoke_puff_2 sonar_pulse sparkle_burst sparkle_burst_2 sparkle_burst_3 sparkler speed_burst stun_stars stun_stars_2 stun_stars_3 sword_slash tank_burning tank_explosion terrain_blast top_collision_impact water_burst water_splash wind_leaves
|
|
56
|
+
flipbook: blood_mist blue_flame bubble_float dust_puff electric_spark ember_glow explosion_burst gray_smoke heart_pulse hot_spark_burst leaf_flutter magic_sparkle muzzle_flash_star orange_flame poison_cloud smoke_puff snowflake_spin star_twinkle tracer_streak water_splash white_smoke
|
|
54
57
|
</asset_word_library>
|
|
55
58
|
|
|
56
59
|
<texture_dsl_usage>
|
|
57
60
|
- For new worlds using primitive meshes, use texture dsl to paint them.
|
|
58
61
|
- `#texture Name ... #end` auto-creates triplanar `material Name`. Use it as `material = "Name"` in scene objects, `Material.Name` in Weave code, `terrain.SetTerrainMaterial(Material.Name)`, `terrain.SetTerrainLayers(...)`, `batch.SetBatchMaterial(Material.Name)`, or as a local `#mesh` slot name (`material: "Name"` / `paint(..., material: "Name")`).
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
+
- Masonry patterns (pattern.brick/tile) under the default triplanar mapping project world-space joints; on carved/round geometry (columns, moldings, statues) use a coursing-free variant or `mapping = uv;`.
|
|
63
|
+
- Flat color shorthand: `#texture TeamRed color=#B03A2E` creates a non-emissive triplanar `material TeamRed` — for stylized color-blocking and quick placeholders; surfaces meant to read as real should use the built-in library (next clause). Optional `roughness=0..1` and `metallic=0..1` (e.g. `#texture RubberFloor color=#1E2024 roughness=0.95`) — without a high roughness, dark ground colors read pale from sky reflection.
|
|
64
|
+
- Built-in material library — real textured PBR with zero declarations: `material = "Wood"` on any object/batch/terrain (and `Material.Wood` in code) just works; the engine copies the one-line library clause into the world on save, and a world-declared material of the same name always wins. Library: Wood, WoodDark, Plank, Bark, Stone, Rock, Brick, Dirt, Sand, Metal, MetalDark, Gold, Fabric, Cloth, Rope, Straw, Leather, Foliage, Leaf, Grass, Snow, Ceramic, Bone, Glass, GlassTransparent, Water, Default; authored surfaces StylizedBricks, OldTilesWhite, StylizedGrass, PolishedConcrete, Asphalt. Default to these for anything meant to read as a real surface.
|
|
65
|
+
- Derived material (color variant, no extra texture bake): `material WoodRed { base = "Wood" tint = "#8a2b1e" }` copies shader/textures/params from the base material and multiplies its color by tint (quoted hex). `base` may name any built-in above or a tintable near-white detail family (WoodDetail, StoneDetail, MetalDetail, FabricDetail, LeatherDetail, PlasterDetail). Prefer this over writing a `#texture` body when only the color differs.
|
|
66
|
+
Material clauses also accept `emission = N` (self-lit glow in the material's tinted color; linear: 1 soft, 4 lamp, 16+ neon bloom) and `roughness = 0..1` / `metallic = 0..1` (uniform overrides of the baked surface response) — e.g. `material CyanGlow { base = "MetalDetail" tint = "#19D9FF" emission = 5 }`.
|
|
67
|
+
- Use #texture directly on primitives, terrain, batches, and local `#mesh` slots. For #mesh material precedence: object/batch override > explicit submesh override > authored local mesh slot material > intrinsic GLB PBR. Imported GLB material names are not auto-bound to world materials. `tile_m` = meters per pattern repeat (default 1.0, prop scale): RAISE to `2.0;`..`4.0;` for large terrain/floors so repeats are not too tiny; LOWER to `0.3;`..`0.5;` for small props/panels so the pattern does not print larger than the object. Material clauses accept it too (`material X { base = "Fabric" tileM = 0.4 }`).
|
|
62
68
|
- Authoring or editing a `#texture` BODY (anything beyond the color=/base= one-liners above): read the `texture_dsl` skill first — statement syntax, builtins (noise/pattern/sdf/filter families), recipes, worked examples. Do not guess builtin names or signatures from memory.
|
|
63
69
|
- Terrain layer sets: assign scalar `out.height` on every layer of the set (or none) — higher-height texels win at SetTerrainLayers blend borders (rock pushing through grass).
|
|
64
70
|
- `#terrain_splat Name ... #end` + `terrain.SetTerrainSplat("Name")`: same expression language with terrain inputs `height` (world m), `slope01` (0 flat..1 vertical), `convex` (-1..1), `ao` (0..1), `moist` (0..1), `user` (0..1, fed by `terrain.SetTerrainSplatMask(mask, sx, sy)`, else 0); `out.color` RGB = SetTerrainLayers Steep/High/Low weights (base = leftover). Baked per-terrain; replaces the slope/height auto-bands for hand-authored placement (e.g. `out.color = vec3(smoothstep(0.4, 0.6, slope01), smoothstep(18, 24, height), sat(moist * 2 - convex));`).
|
|
65
71
|
- Optional scalar `out.grass` in `#terrain_splat` = grass spawn density 0..1 (0 = bare; spawn saturates around 0.4, so mid values still read as full cover) - keeps grass off beaches/cliffs/dry zones from the same rules that place the textures, and thins the auto ground tint with it. Unassigned = full density; an explicit SetTerrainGrassWeightmap overrides it.
|
|
66
72
|
- Colormap mode: `out.albedo` (vec3, mutually exclusive with `out.color`) makes the bake the FINAL surface color instead of layer weights - full per-pixel control (satellite-style coloring, stylized palettes, drainage streaks from `user`). The base SetTerrainMaterial still supplies normal/ORM detail; SetTerrainLayers weights, band blending, macro tint and the auto grass ground tint are all bypassed. Set `size = 2048;` (up to 4096) for crisp color; `out.grass` still works alongside.
|
|
67
|
-
- `#road_network Name ... #end`: a road GRAPH the engine meshes into junction-merged road tiles at load (carriageway, miter-blended intersections, curbs, sidewalks, center dashes, crosswalks). Body statements end with `;`: `cell = 32000;` (tile grid cm, optional), `class = width_cm, sidewalk_cm, curb_w_cm, curb_h_cm, "CarriagewayMat", "CurbMat", "SidewalkMat", "MarkingMat";` (repeatable; edges index classes in order; empty marking name = no markings), `nodes = x, y, x, y, ...;` (cm, repeatable/appends), `edges = nodeA, nodeB, classIndex, ...;` (triplets, repeatable)
|
|
68
|
-
- `#terrain_bytes "terrain.url" ... #end`: embedded uploaded-terrain heightfield payload (base64 of zlib-compressed terrain bytes) satisfying `terrain X { model = "terrain.url" }` with zero network fetches, so the world is self-contained
|
|
69
|
-
-
|
|
73
|
+
- `#road_network Name ... #end`: a road GRAPH the engine meshes into junction-merged road tiles at load (carriageway, miter-blended intersections, curbs, sidewalks, center dashes, crosswalks). Body statements end with `;`: `cell = 32000;` (tile grid cm, optional), `class = width_cm, sidewalk_cm, curb_w_cm, curb_h_cm, "CarriagewayMat", "CurbMat", "SidewalkMat", "MarkingMat";` (repeatable; edges index classes in order; empty marking name = no markings), `nodes = x, y, x, y, ...;` (cm, repeatable/appends), `edges = nodeA, nodeB, classIndex, ...;` (triplets, repeatable), `curve = a, b, c1x, c1y[, c2x, c2y];` (optional per a→b edge: quadratic/cubic bezier control points in cm; omit = straight; junctions meet curves at their end tangents; too-tight curvature for the class width is a load error). The block spawns the network itself: tiled road meshes plus walkable per-edge slabs (no companion object needed); optional `position = x, y, z;` offsets the whole network. Editor edits fold back into this block on save — re-read it before patching. Prefer this over hand-built per-segment road boxes: junctions come out clean and one graph edit reflows everything.
|
|
74
|
+
- `#terrain_bytes "terrain.url" ... #end`: embedded uploaded-terrain heightfield payload (base64 of zlib-compressed terrain bytes) satisfying `terrain X { model = "terrain.url" }` with zero network fetches, so the world is self-contained offline. Blocks are machine-generated (desktop `embed_terrain` CLI), not hand-authored - and never required to submit: validation resolves uploaded `terrain.` assets itself. Prefer procedural terrain (`SpawnTerrainFromHeights` + `TerrainHeights*`) for new worlds.
|
|
75
|
+
- Emission is linear (1 soft self-lit, 4 lamp, 16+ neon bloom): put it on small accents - lamps, signs, runes, windows; large emissive surfaces wash out the scene, keep those <= 1.
|
|
70
76
|
</texture_dsl_usage>
|
|
71
77
|
|
|
72
78
|
<adjuster_usage>
|
|
73
|
-
- `#adjuster ... #end` (top-level): declares hand-tunable global look knobs
|
|
79
|
+
- `#adjuster ... #end` (top-level): declares hand-tunable global look knobs. Values apply on load; slider edits fold back into this block on save. Sliders: grass/ground knobs in the Terrain inspector (click the terrain), SetWater* in Find > Water, the rest in the Adjust panel (top-right sliders icon; links to both inspectors).
|
|
74
80
|
- Body lines: `section "Title"` groups knobs after it; `knob <SimCallName> = <value> [range a..b]`; value is a number, or `#rrggbb` for color knobs; `range` overrides the slider range of number knobs. `//` comments ok.
|
|
75
81
|
- Only whitelisted global sim calls (below) are valid knob names; each at most once. Texture params and ranged #mesh params expose per-texture/per-mesh panel knobs on their own; no #adjuster entry for those.
|
|
76
|
-
- Add an #adjuster block whenever the look is a matter of taste the user may want to hand-tune (grass,
|
|
82
|
+
- Add an #adjuster block whenever the look is a matter of taste the user may want to hand-tune (grass, ground, water, wind, tree LOD).
|
|
83
|
+
- Lighting/sky/grade knobs (sun color/intensity, skybox tint/saturation/exposure, SSAO, vignette, scene contrast/saturation/exposure) are USER-owned: never write or retune them, as knobs or calls; lighting/mood requests -> Adjust panel.
|
|
77
84
|
- SetWater* knobs set the INITIAL ocean state: they replay after load-time code, so a `knob SetWaterEnabled = 0` line keeps water off across reloads (the editor's Find > Water delete writes it). Code that changes water after load (OnSpawned, timers, events) still wins at runtime.
|
|
78
|
-
- Knobs: SetGrassRootColor (color), SetGrassTipColor (color), SetGrassBrightnessVariationPercent (0..300), SetGrassSizeVariationPercent (0..300), SetGrassHeightMinCm (0..300), SetGrassHeightMaxCm (0..300), SetGrassWavePeriodCm (100..3000), SetGrassWindStrengthCm (0..50), SetGrassWindSpeedPercent (0..300), SetGrassBladeWidthPercent (50..200), SetGrassSpacingCm (0..100), SetTerrainErosionDetailPercent (0..300), SetSunColor (color), SetSunIntensity (0..20), SetSkyboxTint (color), SetSkyboxSaturation (0..3), SetSkyboxExposure (0..3), SetSsaoIntensity (0..3), SetTerrainNormalStrengthPercent (0..300), SetContrast (0..4), SetSaturation (0..4), SetExposure (
|
|
85
|
+
- Knobs: SetGrassRootColor (color), SetGrassTipColor (color), SetGrassBrightnessVariationPercent (0..300), SetGrassSizeVariationPercent (0..300), SetGrassHeightMinCm (0..300), SetGrassHeightMaxCm (0..300), SetGrassWavePeriodCm (100..3000), SetGrassWindStrengthCm (0..50), SetGrassWindSpeedPercent (0..300), SetGrassBladeWidthPercent (50..200), SetGrassSpacingCm (0..100), SetGrassTerrainActiveDistanceM (0..500), SetGrassCullDistanceM (0..500), SetGrassFadeDistancePercent (0..100), SetGrassFarThinningPower (0..8), SetTerrainErosionDetailPercent (0..300), SetSunColor (color), SetSunIntensity (0..20), SetSunRotation (0..360), SetSunTilt (0..90), SetSkyLightIntensity (0..8), SetSkyLightColor (color), SetSkyLightSaturation (0..3), SetSkyboxTint (color), SetSkyboxSaturation (0..3), SetSkyboxExposure (0..3), SetSkyboxHaze (0..8), SetSkyboxContrast (0..3), SetSkyboxGroundColor (color), SetSkyboxColor (color), SetSkyboxCloudCoverage (0..1), SetSkyboxStarIntensity (0..3), SetShadowBlurRadius (0..20), SetShadowOpacity (0..1), SetShadowLift (0..3), SetShadowLiftThreshold (0.02..0.3), SetShadowDistance (0..500), SetFogColor (color), SetFogStartDistance (0..2000), SetFogEndDistance (0..2000), SetFogSkyboxAffect (0..100), SetHeightFogEnabled (0..1), SetHeightFogTop (0..200), SetHeightFogBottom (-100..200), SetHeightFogSoftness (0..1), SetSsaoIntensity (0..1), SetSsaoDistance (0..10), SetBloomThreshold (0..3), SetBloomIntensity (0..2), SetTerrainNormalStrengthPercent (0..300), SetContrast (0..4), SetSaturation (0..4), SetExposure (-3..3), SetVignetteStrength (0..1), SetVignetteRadius (0..1), SetVignetteSoftness (0..1), SetVignetteColor (color), SetWaterEnabled (0..1), SetWaterSeaLevelCm (-2000..10000), SetWaterAmplitudeCm (0..500), SetWaterWavelengthCm (100..20000), SetWaterSteepness (0..1), SetWaterDirectionDeg (0..360), SetWaterSpeedPercent (0..400), SetWaterShallowColor (color), SetWaterDeepColor (color), SetWaterColorMixRangeCm (1..2000), SetWaterTransparency (0..1), SetWaterReflectionStrength (0..2), SetWaterReflectionDistortion (0..2), SetWindDirectionDeg (0..360), SetWindStrength (0..1), SetWindGustiness (0..1), SetTreeLodSwitchDistanceM (0..300), SetTreeMeshDistanceM (0..1000), SetTreeCullDistanceM (0..3000), SetTreeFadeDistancePercent (5..80), SetTreeFarLeafDensityPercent (10..300)
|
|
79
86
|
Example:
|
|
80
87
|
#adjuster
|
|
81
88
|
section "Grass"
|
|
82
89
|
knob SetGrassTipColor = #8fbf4a
|
|
83
|
-
knob SetGrassHeightMaxCm = 45
|
|
84
|
-
knob SetSunIntensity = 4.5 range 2..8
|
|
90
|
+
knob SetGrassHeightMaxCm = 45 range 0..120
|
|
85
91
|
#end
|
|
86
92
|
</adjuster_usage>
|
|
87
93
|
|
|
88
94
|
<particles_json_usage>
|
|
89
95
|
- `#particles Name ... #end` defines local effect asset `effect.Name`; `#particles effect.Name` also works, but prefer short `Name`. Body is a JSON array; each object is one emitter component.
|
|
90
96
|
- Every local component needs a top-level `"particleTextureName"`: `"flipbook.<word>"` for an animated sprite-sheet atlas from the shared flipbook library (best for fire/smoke/magic; lowercase subject words like `flipbook.orange_flame`; misses render a glow placeholder until the word is minted), `"texture.TextureName.color"` with a matching `#texture TextureName`, or `"image.<User>.<Name>"` referencing a real ori image asset you actually know (never invent one). Use `texture.*.color` (`baked.*` addresses material-slot maps, not particle textures; `.normal`/`.orm` are not particle flipbooks).
|
|
91
|
-
- Spawn with scene `effect Spark { effect = "effect.Name" }` or
|
|
92
|
-
- Sizing: declare `"boundingBoxSize": [x, y, z]` (meters, Z-up; per-component, max wins) as the effect's authored real-world footprint, and author emitters (`radius`, `sizeSequence`, speeds) at that real scale. The object's `size` (cm, like any object) then scales the effect by `size / boundingBoxSize` — e.g. a campfire authored with `boundingBoxSize [1, 1, 1.5]` placed with `size = 100` renders at its authored 1m footprint. Without `boundingBoxSize` the base is a legacy 10cm box, so `size = 100` blows the effect up 10x — always declare it. Size effects with `size`, not `scale`.
|
|
97
|
+
- Spawn with scene `effect Spark { effect = "effect.Name" }` - or, for prop-attached fire/smoke, prefer a `particles("<word>", at: ...)` socket inside the `#mesh` (see <mesh_dsl_usage>). From code pass the ASSET as a static import: `SpawnEffectWithAutoDestroy(Ori.effect.Name, pos)` (returns the spawned object — `.SetSizeUniform(n)` to size it), or `Ori.alias.effect.<word>` for aliases. A `Scene.<object>` reference is not an effect asset (W2C0405).
|
|
98
|
+
- Sizing: declare `"boundingBoxSize": [x, y, z]` (meters, Z-up; per-component, max wins) as the effect's authored real-world footprint, and author emitters (`radius`, `sizeSequence`, speeds) at that real scale. The object's `size` (cm, like any object) then scales the effect by `size / boundingBoxSize` — e.g. a campfire authored with `boundingBoxSize [1, 1, 1.5]` placed with `size = 100` renders at its authored 1m footprint. Without `boundingBoxSize` the base is a legacy 10cm box, so `size = 100` blows the effect up 10x — always declare it. Size effects with `size`, not `scale`. Author emitters freely around your origin; when the content is not origin-centered, add `"boundingBoxCenter": [x, y, z]` (meters; where the box CENTER sits relative to that origin; default [0,0,0]) so the box covers the content — e.g. a ground flame rising 0..1.5m declares [0, 0, 0.75]. Placement matches meshes: the object's `position` rests the declared box's bottom on the point (`transform_pivot = center` centers it); code spawns (`SpawnEffect*`) put the box CENTER at the given position.
|
|
93
99
|
- JSON values: scalars are numbers, vecs are `[x,y]`/`[x,y,z]`, curves are up to 8 `[[life01, value], ...]`; `colorSequence` uses `[[life01, [r,g,b]], ...]`. Prefer numeric `0/1` for booleans.
|
|
94
|
-
- CPU/import keys: `_particleName_` ignored label; `particleCount` live-particle capacity override, not spawn count; `emitterPosition`/`emitterRotation` component-local offset/XYZ degrees; `boundingBoxSize` authored footprint in meters (see sizing bullet above).
|
|
100
|
+
- CPU/import keys: `_particleName_` ignored label; `particleCount` live-particle capacity override, not spawn count; `emitterPosition`/`emitterRotation` component-local offset/XYZ degrees; `boundingBoxSize`/`boundingBoxCenter` authored footprint + box placement in meters (see sizing bullet above).
|
|
95
101
|
- Flipbook keys: `framesX`, `framesY`, `totalFrames`, `flipbookAnimationSpeed`, `flipbookStartRandom`. For `flipbook.<word>` refs these auto-fill from the library at load; set them only to override.
|
|
96
|
-
- Spawn keys: `spawnStartDelay`, `spawnRate` per sec, `isLooping`, `duration` seconds (`0` means no shader stop; use finite duration for one-shots), `burstParticleCount
|
|
97
|
-
- Shape keys: `emitterShapeType` = `"sphere"|"box"|"cylinder"|"disc"` or `0..3`; `shapeStyle` = `"volume"|"surface"`; `shapeInOut` = `"in"|"out"|"both"` (omitted defaults inward); `radius`, `cylinderHeight`, `boxSize`, `shapePartial`, `lifeTimeRange`, `speedRange`, `spreadAngle` `[yaw,pitch]` degrees, `lockedToEmitter
|
|
98
|
-
- Visual/motion keys: `sizeConstant` or `sizeSequence`; `opacityConstant` or `opacitySequence`; `colorConstant` or `colorSequence`; `accelerationVector` authored Z-up; `drag` seconds to halve velocity; `rotationRange`, `rotationSpeedRange`; `orientation` 0 facing camera / 1 world-up / 2 velocity-parallel / 3 velocity-perpendicular; `additiveBlend`, `isHidden`, `softParticleFadeDisabled`, `softParticleFadeDistance`, `squashConstant` or `squashSequence` (positive stretches up, negative sideways).
|
|
102
|
+
- Spawn keys: `spawnStartDelay`, `spawnRate` per sec, `isLooping`, `duration` seconds (`0` means no shader stop; use finite duration for one-shots), `burstParticleCount` (fires at t=0, then every `burstInterval` seconds while emitting; `burstInterval` unset = the t=0 burst only).
|
|
103
|
+
- Shape keys: `emitterShapeType` = `"sphere"|"box"|"cylinder"|"disc"` or `0..3`; `shapeStyle` = `"volume"|"surface"`; `shapeInOut` = `"in"|"out"|"both"` (omitted defaults inward); `radius`, `cylinderHeight`, `boxSize`, `shapePartial`, `lifeTimeRange`, `speedRange`, `spreadAngle` `[yaw,pitch]` degrees, `lockedToEmitter` (`0` default = particles keep world position once spawned - trails stay behind a moving emitter; `1` = particles ride it, e.g. torch flame). Box surface emits from +Z face; cylinder height is local X; disc uses `cylinderHeight` as thickness. Emission direction: each shape emits along its own radial direction; `shapeInOut` omitted = INWARD, `"out"` flips outward, `"both"` = 50/50. `spreadAngle [yaw, pitch]` then jitters that direction by uniform +/-yaw degrees about the emitter's local Y axis and +/-pitch degrees about its X axis (fixed local axes, not a cone around the direction).
|
|
104
|
+
- Visual/motion keys: `sizeConstant` or `sizeSequence`; `opacityConstant` or `opacitySequence`; `colorConstant` or `colorSequence`; `accelerationVector` authored Z-up; `drag` seconds to halve velocity; `rotationRange`, `rotationSpeedRange`; `orientation` 0 facing camera / 1 world-up / 2 velocity-parallel / 3 velocity-perpendicular; `additiveBlend`, `isHidden`, `softParticleFadeDisabled`, `softParticleFadeDistance`, `squashConstant` or `squashSequence` (positive stretches up, negative sideways); `lit` 1 = sun+sky shading (smoke/dust/debris read as lit volumes; leave 0 for emissive fire/glow).
|
|
99
105
|
- Resolution key: components render at full (native) resolution by default (crisp edges, no distance shimmer). Set `"renderResolution": "half"` on soft/volumetric high-overdraw components (smoke, fog, glow) — cuts GPU fill cost on weak GPUs; avoid on hard-edged/crisp content (`"full"` is accepted as an explicit no-op).
|
|
100
|
-
- Ribbon/trail components (boost trails, sword arcs, tracers, dash streaks): `"type": "ribbon"` renders one connected camera-facing strip through the emitter's recent world path instead of billboards; layer with billboard components in the same effect (ideal boost = ribbon streak + sparse additive orbs). Keys: `trailSeconds` (point max age = trail length, default 0.5), `pointsPerSecond` (path sampling rate, default 30), `ribbonUv` = `"stretch"` (default) | `"repeat_per_meter"` (tiles by real distance), `ribbonPoints` (ring cap; defaults to fit trailSeconds*pointsPerSecond, max 256); `sizeConstant`/`sizeSequence` = strip WIDTH in meters over point age; texture/flipbook, color/opacity, `additiveBlend`, `renderResolution`, `boundingBoxSize`, `emitterPosition`/`emitterRotation`, soft-fade keys keep their exact meaning. Emission/shape/motion keys (`spawnRate`, `lifeTimeRange`, `emitterShapeType`, `speedRange`, `spreadAngle`, `drag`, `orientation`, `duration`, ...) are load ERRORS on a ribbon component. Ribbon effects are always continuous; the strip exists only while the emitter MOVES (attach to a moving object; `preview_effect` sweeps the emitter for you). Object `size` scales strip width — trail length is temporal (`trailSeconds` x actual speed).
|
|
106
|
+
- Ribbon/trail components (boost trails, sword arcs, tracers, dash streaks): `"type": "ribbon"` renders one connected camera-facing strip through the emitter's recent world path instead of billboards; layer with billboard components in the same effect (ideal boost = ribbon streak + sparse additive orbs). Keys: `trailSeconds` (point max age = trail length, default 0.5), `pointsPerSecond` (path sampling rate, default 30), `ribbonUv` = `"stretch"` (default) | `"repeat_per_meter"` (tiles by real distance), `ribbonPoints` (ring cap; defaults to fit trailSeconds*pointsPerSecond, max 256); `sizeConstant`/`sizeSequence` = strip WIDTH in meters over point age; texture/flipbook, color/opacity, `additiveBlend`, `renderResolution`, `boundingBoxSize`, `emitterPosition`/`emitterRotation`, soft-fade keys keep their exact meaning. Emission/shape/motion keys (`spawnRate`, `lifeTimeRange`, `emitterShapeType`, `speedRange`, `spreadAngle`, `drag`, `orientation`, `duration`, ...) are load ERRORS on a ribbon component. Ribbon effects are always continuous; the strip exists only while the emitter MOVES (attach to a moving object; `preview_effect` sweeps the emitter for you). Object `size` scales strip width — trail length is temporal (`trailSeconds` x actual speed). A trail that looks unchanged after resizing its object wants `trailSeconds`/`sizeSequence` edits, not more `size`.
|
|
101
107
|
- Mesh components (debris, coins, leaves, shards, casings): `"type": "mesh"` renders lit opaque 3D instances of a real mesh instead of sprites; layer with billboard/ribbon components in one effect (rock burst = mesh chunks + dust billboards + one flash). `"particleMeshName"`: a local `"mesh.Name"` or a bare-alias `"alias.model.<noun>"` (library-bound or background-generated; placeholder cube until it lands) — or an array of up to 4, picked per particle (identical chunks read fake). Meshes keep their own materials and receive scene lighting: `colorConstant`/`colorSequence` tints (omit = untinted), `opacityConstant`/`opacitySequence` dither-fades, `sizeConstant`/`sizeSequence` = largest mesh dimension in meters. `"meshAlign"`: `"tumble"` (default; `"tumbleSpeedRange": [min,max]` deg/s random-axis, default [90, 360]) | `"velocity"` (mesh +Z along travel: arrows, shards) | `"world"` (authored orientation). Emission/shape/motion keys keep their exact billboard meaning. Texture/flipbook keys, `orientation`, `rotationRange`/`rotationSpeedRange`, `squash*`, `additiveBlend`, `softParticleFade*`, `renderResolution` are load ERRORS on a mesh component. `"castShadow": 1` enables shadow casting (default off; a fading particle stops casting, like skinned deaths). `"groundBounce": 0..1` bounces world-space particles off terrain/static ground with that restitution and lets them settle until life ends (visual bounce, not physics; `lockedToEmitter` particles skip it). HDR `colorConstant`/`colorSequence` excess over 1.0 becomes emissive glow. Each particle is a real lit draw — keep `spawnRate * lifeTime` under ~100 and meshes low-poly.
|
|
102
108
|
- Runtime-managed fields are not useful in JSON: `time`, `delta_time`, `world_matrix`, `world_matrix_inv`, `emitter_time`, `emitter_id`, `system_scale`, `instance_tint`, `instantClearOnRemove`, padding fields. Unknown keys only log warnings.
|
|
103
109
|
Example:
|
|
@@ -123,32 +129,48 @@ effect ChimneySmoke {
|
|
|
123
129
|
}
|
|
124
130
|
</particles_json_usage>
|
|
125
131
|
|
|
132
|
+
<sound_dsl_usage>
|
|
133
|
+
- `#sound Name ... #end` bakes a local procedural sound asset `sound.name` at load (name lowercased; reference case-insensitive, no creator segment). Play like any sound url: `PlaySound(Ori.sound.name, 1.0)`.
|
|
134
|
+
- Body: `duration = <sec>` (max 8, default 1), `let x = <expr>` chains, final `out = <expr>`. Combine with `+ - * /` and parens; scalar constants broadcast, shorter signals zero-pad.
|
|
135
|
+
- Ops: `osc(sin/saw/square/tri, freq, phase)` (freq may be a signal for sweeps/FM), `noise(white/pink, seed)`, `biquad(lp/hp/bp, input, cutoff, q)` (cutoff may be a signal), `env_adsr(attack, decay, sustain, release)`, `env_exp(start, end, time)` (freq or amp ramps), `mix(a, b, t)`, `layer(a, b, ...)`, `delay(input, time, feedback, mix)`, `pitch(input, ratio)`, `normalize(input, peak)`, `limit(input, ceiling)`.
|
|
136
|
+
- Edits rebake on reload. Keep one-shots short; looping ambience via PlaySoundLooped works on any baked sound.
|
|
137
|
+
- Music/BGM: this synth is SFX-grade (8s cap, no instruments) and cannot produce music — say so up front and use a marketplace/uploaded sound url with PlaySoundLooped instead of iterating synth attempts.
|
|
138
|
+
#sound laser
|
|
139
|
+
duration = 0.35
|
|
140
|
+
let sweep = osc(saw, freq = env_exp(1800, 250))
|
|
141
|
+
out = normalize(biquad(lp, sweep, cutoff = 2200, q = 1.2) * env_adsr(0.005, 0.1, 0.0, 0.08), 0.9)
|
|
142
|
+
#end
|
|
143
|
+
</sound_dsl_usage>
|
|
144
|
+
|
|
126
145
|
<mesh_dsl_usage>
|
|
127
|
-
- `#mesh <Name> ... #end` defines a local CPU-baked procedural static mesh; reference it as `model = "mesh.Name"` in scene declarations or `Ori.model.mesh.Name` in Weave code (SpawnObject,
|
|
146
|
+
- `#mesh <Name> ... #end` defines a local CPU-baked procedural static mesh; reference it as `model = "mesh.Name"` in scene declarations or `Ori.model.mesh.Name` in Weave code (SpawnObject, CreateStaticInstanceBatch). Authoring units are simulation centimeters, Z-up; baked meshes are bottom-center anchored at object origin and their deterministic cm AABB supplies box-collider bounds.
|
|
147
|
+
- `#mesh <Name> destructible: masonry|wood|rock|concrete` bakes a hidden debris chunk set for the model; `obj.ShatterAndDestroy()` then bursts the object into tumbling chunks that bounce, settle, and fade (display-only, no sim collision; without the tag it acts as plain Destroy()). Preview with the preview_destruction tool; destructible mesh names cap at 46 chars.
|
|
128
148
|
- For standard props (tree, crate, market_stall, ...), PREFER a bare `alias model.<noun>` (see <asset_aliases>): it binds a matching generated mesh from the library at load, or background-generates one.
|
|
129
149
|
- Authoring or editing a `#mesh` body: read the `mesh_dsl` skill first — full language + builtin reference and the preview workflow. Do not guess builtin names or signatures from memory; unknown, duplicate, or wrong-type arguments fail loudly.
|
|
150
|
+
- Lights on meshes: an `emitter("Bulb", at:, color:, intensity:, kind: "point"|"spot", ...)` line in the mesh body bakes a light socket; every placed instance shines from it (lamps, signs, projectors) - no separate light object needed. Syntax in the `mesh_dsl` skill; per-object toggle `Object.SetEmitterLightsEnabled(false)`.
|
|
151
|
+
- Effects on meshes: `particles("<effect word>", at: (x,y,z)[, axis:, scale:])` in the mesh body embeds a particle effect the way `emitter()` embeds a light - mesh-local, riding every instance/rotation/saved object. The word must be a quoted literal and resolves like `effect.<word>` (local `#particles` block name, case-sensitive, or lowercase library word). Rest-pose like emitter(); prefer loops (fire, smoke, drips). For prop-attached fire/smoke prefer this over hand-placed effect objects.
|
|
130
152
|
- Material slots: `material: "Name"` on generators or `paint(g, material: "Name")`. A slot name resolves to world `material.Name` when it exists (pair slots with `#texture Name` for low-poly color-blocking). Precedence: object/batch material override > explicit submesh override > authored local slot material > intrinsic GLB PBR.
|
|
131
|
-
- Canonical slot names auto-fill from the built-in material library when the world doesn't declare them (library meshes only use these
|
|
153
|
+
- Canonical slot names auto-fill from the built-in material library when the world doesn't declare them (library meshes only use these; name list in <texture_dsl_usage>). Recolor one for every library mesh by declaring it, e.g. `material Wood { base = "WoodDetail" tint = "#8a2b1e" }`.
|
|
132
154
|
- GlassTransparent is the real translucent glass (fresnel reflection + grime; casts no shadow) - only for panes with real geometry behind them (balustrades, canopies, display cases), NOT hollow building window walls (blend reveals the empty shell; use `interior_window_pbr`, next clause). Derive tints in one line - `material PoolGlass { base = "GlassTransparent" tint = "#3A7E8C" }` - and override `opacity = 0..1` (transmission) / `glassRoughness = 0..1` (reflection blur) only to tune.
|
|
133
|
-
- Hollow building window walls (where GlassTransparent would expose the empty shell): `material TowerWin { base = "InteriorRooms" shader = "interior_window_pbr" tint = "#D8CFC0" glassTint = "#3A4E4A" }` - opaque fake-parallax rooms, night-lit cells automatic. `tint` = mullions; `roomW/roomH/roomDepth/bayW` m override 4.41/3.3/4.0/1.47.
|
|
155
|
+
- Hollow building window walls (where GlassTransparent would expose the empty shell): `material TowerWin { base = "InteriorRooms" shader = "interior_window_pbr" tint = "#D8CFC0" glassTint = "#3A4E4A" }` - opaque fake-parallax rooms, night-lit cells automatic. Base picks the interior per building: `"InteriorRooms"` homes, `"InteriorOffice"` workplaces. Homes get side drapes by default (`drapeSides` = per-side width 0..0.35, 0 disables; offices default 0). `tint` = mullions; `roomW/roomH/roomDepth/bayW` m override 4.41/3.3/4.0/1.47 - repeat PITCHES of the fake-room grid (set to the real floor/bay pitch), not opening sizes; `roomOffsetX/roomOffsetZ` m shift the grid datum (default 0).
|
|
134
156
|
</mesh_dsl_usage>
|
|
135
157
|
|
|
136
158
|
<tree_dsl_usage>
|
|
137
159
|
- `#tree Name ... #end` (top-level): procedural tree species `tree.Name` for scene objects (`model = "tree.Name"`). Deterministic: trunk + branches become the sim collider; the renderer adds detail, leaf cards, LODs, impostors, and wind sway automatically.
|
|
138
|
-
- Body is integer-only `key: value` lines (all optional; defaults are a curated broadleaf oak, so `seed:` alone gives a good tree). Keys: seed, levels (1-4), children (1-16), rings (whorl only: 0-40 branch rings along the trunk, decoupled from segments; 0 = one per segment - reference firs want ~15-20; members auto-stagger vertically), fill (whorl only: 0-16 short branches between rings - dense conifers), segments (2-8), collider_levels (dense conifers should author 1 - collide with the trunk, not hundreds of branchlets), trunk_height (cm), trunk_radius (cm), length_pct, radius_pct, angle_deg (child pitch from the parent axis; >90 emits below horizontal - with up_bias_pct the branch swoops down then up, the fir/spruce shelf), angle_jitter_deg, up_bias_pct, curve_jitter_pct, trunk_lean_deg (whole-trunk tilt, default 2; the trunk-shape low band), trunk_sweep_pct (one C- or S-arc over the trunk height, default 10; the mid band), trunk_noise_pct (trunk wobble amplitude; 0 = dead-straight leader with curve_jitter_pct branches) + trunk_noise_scale_pct (its wavelength as % of trunk height, 8-100, default 35; small = ragged bark-beat, large = slow bends - the high band), trunk_gnarl_pct (0-100; display-only radial lobe noise on the thick lower trunk - buttress/character silhouette for hero close-ups; collider stays smooth), leaves (frond stations per end branch), leaf_size (cm; mean size of one leaf-cluster card - each card is a baked spray of many leaves, not a single leaf; default 150), leaf_base (cm; no leaf cards below this height - bare forks/scaffolds; leaf_base_fade cm band above it ramps density to full, default 120), leaf_shell_pct (0-100; blend toward smooth per-pixel crown-shell shading - 0 = flat per-card facing, 100 = crown shades as a smooth rounded volume; default 100), leaf_clump_pct (0-100; shifts that shell reference from the whole-crown center toward each level-1 branch clump's own center, so big canopies shade as separate puffy lobes instead of one ball; default 40), leaf_detail_pct (0-300; leafy normal-detail sparkle bumped from the frond texture itself; 0 = smooth shell only; default 200), leaf_ao_pct (0-200; canopy AO strength darkening cards near the trunk column and canopy bottom; 0 = off; default 40), leaf_curl_pct (0-300; how strongly leaf cards bow out of their plane for varied, non-flat silhouettes; 0 = flat cards; default 150), leaf_far_shade_pct (0-150; far-billboard brightness; billboards lose cast shadows so <100 darkens them to match the near tree; default 60), droop_pct (tips sag), length_taper_pct (shorter branches higher up; ~70 = conifer cone), root_flare_pct (wider trunk base).
|
|
160
|
+
- Body is integer-only `key: value` lines (all optional; defaults are a curated broadleaf oak, so `seed:` alone gives a good tree). Keys: seed, levels (1-4), children (1-16), rings (whorl only: 0-40 branch rings along the trunk, decoupled from segments; 0 = one per segment - reference firs want ~15-20; members auto-stagger vertically), fill (whorl only: 0-16 short branches between rings - dense conifers), segments (2-8), collider_levels (dense conifers should author 1 - collide with the trunk, not hundreds of branchlets), trunk_height (cm), trunk_radius (cm), length_pct, radius_pct, angle_deg (child pitch from the parent axis; >90 emits below horizontal - with up_bias_pct the branch swoops down then up, the fir/spruce shelf), angle_jitter_deg, up_bias_pct, curve_jitter_pct, trunk_lean_deg (whole-trunk tilt, default 2; the trunk-shape low band), trunk_sweep_pct (one C- or S-arc over the trunk height, default 10; the mid band), trunk_noise_pct (trunk wobble amplitude; 0 = dead-straight leader with curve_jitter_pct branches) + trunk_noise_scale_pct (its wavelength as % of trunk height, 8-100, default 35; small = ragged bark-beat, large = slow bends - the high band), trunk_gnarl_pct (0-100; display-only radial lobe noise on the thick lower trunk - buttress/character silhouette for hero close-ups; collider stays smooth), leaves (frond stations per end branch), leaf_size (cm; mean size of one leaf-cluster card - each card is a baked spray of many leaves, not a single leaf; default 150), leaf_base (cm; no leaf cards below this height - bare forks/scaffolds; leaf_base_fade cm band above it ramps density to full, default 120), leaf_shell_pct (0-100; blend toward smooth per-pixel crown-shell shading - 0 = flat per-card facing, 100 = crown shades as a smooth rounded volume; default 100), leaf_clump_pct (0-100; shifts that shell reference from the whole-crown center toward each level-1 branch clump's own center, so big canopies shade as separate puffy lobes instead of one ball; default 40), leaf_detail_pct (0-300; leafy normal-detail sparkle bumped from the frond texture itself; 0 = smooth shell only; default 200), leaf_ao_pct (0-200; canopy AO strength darkening cards near the trunk column and canopy bottom; 0 = off; default 40), leaf_curl_pct (0-300; how strongly leaf cards bow out of their plane for varied, non-flat silhouettes; 0 = flat cards; default 150), leaf_far_shade_pct (0-150; far-billboard brightness; billboards lose cast shadows so <100 darkens them to match the near tree; default 60), droop_pct (tips sag), length_taper_pct (shorter branches higher up; ~70 = conifer cone), root_flare_pct (wider trunk base), wind_pct (0-300; sway response to the global wind - trunk, branches and leaf cards; 0 = rigid, default 100).
|
|
139
161
|
- Word keys: `mode: spiral|whorl|colonize` (whorl = rings of branches off a straight central leader - the pine/fir/spruce mode; colonize = space-colonization crown grown to fill a shape), `crown_shape: round|cone|vase` (colonize only), `leaf_mode: broadleaf|needle|weeping|mat|frond` (foliage style; weeping = hanging strand curtain, pair with high droop_pct for willows; frond = one whole-branch ribbon card per branch replacing the branch wood - THE fir/spruce mode, pair with mode: whorl and angle_deg ~95-115; mat = scattered flat shelf mats, a lighter conifer fill).
|
|
140
162
|
- Colonize-only integer keys: points (crown attraction point count; auto-scales with crown size up to 1024, override for extra fine/coarse branching), leader (0-100: how far up the crown the central trunk persists before dissolving into forks - 0 = open broadleaf habit, oaks/beeches fork early; ~50-70 = beech/maple with a trunk partway into the crown; 100 = excurrent conifer/poplar spire), crown_width (cm, 0 = auto), crown_base (crown bottom height cm, 0 = auto; whorl mode also honors it as the lowest branch ring - ground-sweeping firs author ~150), crown_follow_pct (0-100, default 75: how much the crown envelope rides a leaning/swept trunk's top - 100 = crown centered on the bole, 0 = crown stays over the root and limbs reach back, the "leaning toward a clearing" habit).
|
|
141
163
|
- Colonize authored crown shapes: `crown_sphere: x, y, z, r` / `crown_ellipsoid: x, y, z, rx, ry, rz` / `crown_dome: x, y, z_cut, rx, ry, rise` lines (cm, up to 8) grow branches to fill the union of those volumes, overriding crown_shape/crown_width/crown_base. A dome is the upper half of an ellipsoid with a flat cut at z_cut (umbrella/mushroom caps). Keep volumes near/above the trunk; disconnected lobes get bridged by a reaching branch. Colonize branches stay inside their envelope, so droop_pct barely bends them - weeping/cascading silhouettes (willows) need mode: spiral with high droop_pct instead.
|
|
142
164
|
- Authored trunk path (any mode): `trunk_point: x, y, z` lines (cm, up to 8, z strictly ascending) draw the trunk's xy course as a smooth spline through the knots, replacing trunk_lean_deg/trunk_sweep_pct (trunk_noise still layers on top). Above the top knot the offset holds, so put the top knot at trunk_height (or crown_base) for a fully drawn bole; pair with crown volumes + crown_follow_pct 100 for a hero leaning tree. Note: trees in a #forest keep a straight vertical collision capsule - keep heavy lean/sweep/trunk_point species out of forests or accept coarse trunk collision there.
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
- Global wind: `SetWind(DirectionDeg, Strength01, Gustiness01)`.
|
|
165
|
+
- Leaf card: `leaf: <TextureName>` body key (or naming the texture `TreeLeaf_<Name>`) replaces the auto-generated frond card; the texture needs `mapping = uv;`, `render_mode = cutout;` + `out.alpha`. `mapping = uv;` is required - cards are UV quads; the triplanar default renders them invisible (unknown or non-uv `leaf:` refs = load error). Author the card as a spray of MANY leaves via `sdf.pinnate` (thin leaflets for needle/frond conifers) or `pattern.foliage`, never one big leaf, with the stem at the bottom edge (v=0) - the card's base sits on the branch. Cluster cards consume only color+alpha; authored `out.normal`/`out.orm` matter only for `leaf_mode: frond` ribbon cards.
|
|
166
|
+
- Bark: `bark: <TextureOrMaterialName>` body key per species (unknown name = load error); bare `TreeBark` (#texture or material) is the shared default for species without one (else built-in bark). Cheap per-species tint: `material PineBark { base = "TreeBark" tint = "#5a4630" }` + `bark: PineBark`.
|
|
167
|
+
- Global wind: `SetWind(DirectionDeg, Strength01, Gustiness01)`; per-species response: `wind_pct`.
|
|
146
168
|
- Authored integer keys expose editor adjuster-panel sliders (live rebake; the sim collider updates on save) - author the look-defining ones. When the look matters, iterate with the `preview_tree` tool before submitting.
|
|
147
169
|
Species recipes: see <example_tree_species> in <world_description_examples>.
|
|
148
170
|
</tree_dsl_usage>
|
|
149
171
|
|
|
150
172
|
<forest_dsl_usage>
|
|
151
|
-
- `#forest Name ... #end` (top-level): scatters a `#tree` species over an object's box region (`model = "forest.Name"`; set the object's `size` (cm) to shape the region, default 20x20x10 m). Each tree stands on the terrain surface under its XY; where that surface lies outside the region's z-span the tree is skipped, so the z-span doubles as an elevation band (e.g. bottom above the waterline = no beach/underwater trees, top below the peaks = a tree line). An XY with no terrain under it is skipped too when the world has terrain (a region overhanging the terrain edge grows no drowned/floating trees); only fully terrain-less worlds anchor on the box bottom (flat-ground worlds just work; deliberate floating platforms use on_terrain: 0). Placements stay stable when the region is resized/moved. THE way to put woods on terrain - one region object, no spawn loops.
|
|
173
|
+
- `#forest Name ... #end` (top-level): scatters a `#tree` species over an object's box region (`model = "forest.Name"`; set the object's `size` (cm) to shape the region, default 20x20x10 m; the region object is bottom-anchored like any object - z-span = [position.z, position.z + size.z]). Each tree stands on the terrain surface under its XY; where that surface lies outside the region's z-span the tree is skipped, so the z-span doubles as an elevation band (e.g. bottom above the waterline = no beach/underwater trees, top below the peaks = a tree line). An XY with no terrain under it is skipped too when the world has terrain (a region overhanging the terrain edge grows no drowned/floating trees); only fully terrain-less worlds anchor on the box bottom (flat-ground worlds just work; deliberate floating platforms use on_terrain: 0). Placements stay stable when the region is resized/moved. THE way to put woods on terrain - one region object, no spawn loops.
|
|
152
174
|
- Body is `key: value` lines. `species: <TreeName>` (required, a `#tree` in this world); optional integers: seed, spacing (cm cell size, >= 100, default 700), density_pct (0-100 chance per cell), scale_min_pct / scale_max_pct (per-tree size range, default 85-125), on_terrain (default 1; 0 = always stand on the box bottom, e.g. floating platform forests).
|
|
153
175
|
- Terrain filters (the precise control; the z-span is the coarse bracket): height_min_cm / height_max_cm keep trees inside a world-z band at their terrain anchor (waterline, tree line); slope_max_pct (0-100 on the #terrain_splat slope01 scale: 50 = 45deg, 35 ~ 30deg) skips steeper ground (no cliff trees). Filters gate terrain-anchored trees only and require on_terrain: 1. Prefer one big region + filters over hand-fitted boxes.
|
|
154
176
|
- Collision is per trunk only (a vertical capsule from the species' trunk), never the region box - players walk between trees and brush through leaves.
|
|
@@ -163,6 +185,21 @@ Example:
|
|
|
163
185
|
object Woods1 { model = "forest.PineWoods" position = (0, 0, 500) size = (12000, 8000, 1000) }
|
|
164
186
|
</forest_dsl_usage>
|
|
165
187
|
|
|
188
|
+
<scatter_dsl_usage>
|
|
189
|
+
- `#scatter Name ... #end` (top-level): scatters ANY model over an object's box region (`model = "scatter.Name"`), exactly like `#forest` placement-wise (same region object, terrain anchoring, z-span band, filters) but display-only: NO collision, plus per-instance color variation. Use for rocks, bushes, props, debris; use `#forest` when trees should collide.
|
|
190
|
+
- Body: `model: <url>` (required; `mesh.<Name>`, `Ori.model.*`, or a market model url) + the `#forest` keys minus `species`; optional integers hue_jitter_deg (0-180), sat_jitter_pct / val_jitter_pct (0-100) tint each instance (multiplies the region object's color; a tint on textured models, not true hue rotation).
|
|
191
|
+
Example:
|
|
192
|
+
#scatter Boulders
|
|
193
|
+
model: "mesh.Rock"
|
|
194
|
+
spacing: 900
|
|
195
|
+
density_pct: 40
|
|
196
|
+
scale_min_pct: 60
|
|
197
|
+
scale_max_pct: 160
|
|
198
|
+
hue_jitter_deg: 12
|
|
199
|
+
#end
|
|
200
|
+
object RockField { model = "scatter.Boulders" position = (0, 0, 500) size = (12000, 8000, 1000) }
|
|
201
|
+
</scatter_dsl_usage>
|
|
202
|
+
|
|
166
203
|
<room_building>
|
|
167
204
|
- For simple rectangular rooms, prefer `BuildRoom(Center, RoomSize, Thickness, Height, GateSide, GateWidth)` instead of hand-placing wall boxes. It creates clean aligned walls and avoids crooked wall edges/gaps.
|
|
168
205
|
- `Center` is the room center position. `RoomSize` is the INNER clear size in cm. Walls sit just outside that footprint, with bottoms at `Center.Z` and height `Height`.
|
|
@@ -196,13 +233,22 @@ event OnSpawned {
|
|
|
196
233
|
- `TileGridCreate(TilesX, TilesY, DefaultWalkable)` returns an opaque compact List. `TileGridCreate(TilesX, TilesY, DefaultWalkable, EdgeBlockingEnabled)` enables optional edge-wall storage when the Bool is true. Do not call generic mutating List functions (`Add`, `Set`, `Resize`, `Clear`, `Pop`, etc.) on it; use TileGrid helpers. Padded grid capacity is capped at 1,048,576 tiles.
|
|
197
234
|
- Edge-blocking grids can use `TileGridSetEdgeBlocked(Grid, X1, Y1, X2, Y2, Blocked)` for cardinal-adjacent tile pairs. Compact grids reject SetEdgeBlocked; `TileGridIsEdgeBlocked(...)` returns false for valid compact-grid edges. Edge-enabled diagonal pathfinding does not cross blocked cardinal edges, and a diagonal step requires all four cardinal edges around the crossed 2x2 tile corner to be clear.
|
|
198
235
|
- `BuildTileBlockedGridFromTag(OriginTile, TilesX, TilesY, BlockerTag)` creates a walkable grid and blocks tiles whose world-center point overlaps an object with the tag. Build/rebuild grids when blockers change, not every frame.
|
|
199
|
-
- `TileGridSetWalkableFromStaticColliders(Grid, OriginTile, ZMin, ZMax)` re-rasterizes an existing
|
|
200
|
-
- `
|
|
236
|
+
- `TileGridSetWalkableFromStaticColliders(Grid, OriginTile, ZMin, ZMax)` re-rasterizes an existing grid from the static colliders: every tile resets, and a tile blocks when any static collider's world AABB overlaps its rect within the [ZMin, ZMax] cm band (rotated colliders use their conservative AABB; colliders covering the whole grid footprint - floors, world bounds - are ignored). Static world objects, static PhysicsObjects, and tilemap solid cells all block; keep ZMin above the walk surface AND above any solid ground tilemap's top (e.g. 20) so floor slabs don't block their tiles. Layered grids rasterize per layer ([ZMin, ZMax] relative to each layer plane). It RESETS every tile - call it BEFORE the FromTerrain pair; for runtime placements stamp instead of re-rastering.
|
|
237
|
+
- `TileGridSetWalkableFromTerrain(Grid, OriginTile, MaxSlope01)` marks tiles UNWALKABLE where the topmost terrain at the tile center is steeper than MaxSlope01 (slope01 = g/(1+g); 0.5 = 45deg; walkers wedge above ~0.5, so 0.4-0.45 is a safe unit ceiling) or absent. Subtractive only - never re-opens tiles.
|
|
238
|
+
- `TileGridSetEdgesFromTerrain(Grid, OriginTile, MaxStepCm)` (edge-blocking grids) blocks the edge between cardinal neighbors whose terrain height difference exceeds MaxStepCm: cliffs become impassable EDGES while rim tiles stay standable; graded ramps (small per-tile rise) stay open. Tile centers sample mid-cell, so a hard TierCm cliff reads as two TierCm/2 steps - MaxStepCm = TierCm/3 separates cliff jumps from ramp rise; size ramps >= 2 tiles of run per tier step so their slope stays below the ~45deg walk limit.
|
|
239
|
+
- Cliff-map load order (manager OnSpawned): collider raster -> FromTerrain -> EdgesFromTerrain -> BakeCrowdObstacles -> IsReachable asserts. Then placements only stamp.
|
|
240
|
+
- `TileGridStampRect(Grid, X1, Y1, X2, Y2, Blocked)` / `TileGridStampObject(Grid, OriginTile, Obj, Blocked)` stamp footprints incrementally (rect corners clamp to the grid; spawn: true, despawn: false re-opens the stamped rect - overlapping footprints must re-stamp their survivors) - cheaper than re-rastering after each placement.
|
|
241
|
+
- `TileGridIsReachable(Grid, X1, Y1, X2, Y2)` (layered: 7-arg form) = "does an 8-dir path exist" without building one - assert connectivity (spawn points, ramps) in a manager OnSpawned with `RaiseWorldError` so broken maps fail at load, not mid-game; also check before wall placements that could seal a region.
|
|
242
|
+
- `TileGridBakeCrowdObstacles(Grid, OriginTile)` bakes avoidance line obstacles from the grid's blocked edges and unwalkable borders so crowd units are never pushed off cliffs or into tilemap walls avoidance cannot see. It is a snapshot: re-bake after runtime walkability edits (opened gates, razed bridges); placed buildings already repel via their own colliders, no re-bake needed.
|
|
243
|
+
- One grid, many movers (RTS): build the grid ONCE on a manager scene object (`var Grid: List<Number>` field; `TileGridCreate` + `TileGridSetWalkableFromStaticColliders` in its OnSpawned) and let every unit reach it through a scene cast - `TileGridFindPath8((Scene.Manager as HQ).Grid, ...)`. Cross-class field access works for List fields; never rebuild the grid per unit.
|
|
244
|
+
- `TileGridFindPath4/8(Grid, StartX, StartY, GoalX, GoalY[, GoalToleranceTiles])` returns a reversed path List: Goal ... Start. Empty means blocked start/goal, out of bounds, or no path (a TRUE verdict - the search budget covers the whole grid, so a no-path query floods the goal's component; on big maps gate mass orders with one TileGridIsReachable, share flow fields, and cache no-path results). GoalToleranceTiles (0-16) accepts the cheapest walkable tile within that ring of the goal - set it when ordering onto colliders (buildings, resource nodes); empty then means the ring itself is unreachable. Compact TileGridFindPath8 preserves corner cutting through unwalkable side tiles; edge-enabled grids still require crossed cardinal edges to be clear.
|
|
201
245
|
- `TileGridHasLineOfSight(Grid, StartX, StartY, GoalX, GoalY)` (layered: 7-arg form, same Z only) returns true when the straight tile-center corridor is fully walkable (crossed edges clear on edge grids; exact corner crossings also need both side tiles). Chasing a moving target: LOS true -> `MoveTowardContinuous` at the live position, false -> FindPath + follow.
|
|
202
246
|
- `TileGridSmoothPath(Grid, Path)` returns a new reversed path with waypoints dropped wherever the straight corridor between the survivors is clear (string pulling; never smooths across a layer change). Walkers zigzagging tile centers -> smooth once after FindPath, before following.
|
|
203
|
-
- To follow a path, call `MoveAlongTilePathContinuous(Path, Grid, OriginTile, Speed)` each frame: one call steps the mover, pops reached waypoints, and returns true once the path is consumed (false for an already-empty path). Prefer it over manual MoveToward + Pop loops; it is one sim call per mover per frame.
|
|
247
|
+
- To follow a path, call `MoveAlongTilePathContinuous(Path, Grid, OriginTile, Speed[, ArriveWithinCm[, OrientTurnRate]])` each frame: one call steps the mover, pops reached waypoints, and returns true once the path is consumed (false for an already-empty path). ArriveWithinCm (default 1) completes the FINAL waypoint within that distance - set it when the goal can be occupied (resource node, building) so contact does not stall arrival. OrientTurnRate faces the mover along its travel (deg/s; 0 = instant) - RTS vehicles read wrong without it. Prefer it over manual MoveToward + Pop loops; it is one sim call per mover per frame.
|
|
248
|
+
- Mover frozen after an early order switch: a half-consumed path re-followed later pushes at a stale waypoint - Clear() the path list whenever you leave a follow before it returns true.
|
|
204
249
|
- For CROWDS (Weave2, hundreds+ of movers): `obj.StartTilePathFollow(Path, Grid, OriginTile, Speed)` registers the mover with the native segment follower (same movement semantics, zero per-mover calls while walking; returns false for an empty path). Start CONSUMES the path list (it is empty afterwards - the follower owns a copy); sim-visible mover positions update at waypoint granularity while walking. One manager then drains `TilePathFollowTakeArrivals()` once per frame (returns finished movers as `List<Object>`) to run arrival logic. `obj.StopTilePathFollow()` cancels; `obj.IsTilePathFollowDone()` polls instead of draining (an arrival stays set until taken/stopped/restarted). Starting again replaces the previous follow.
|
|
205
|
-
-
|
|
250
|
+
- For MANY movers sharing ONE destination set (group move / rally / harvest points): `TileGridFlowFieldBuild(Grid, GoalTiles)` bakes per-tile direction toward the nearest goal into a reusable opaque List (GoalTiles = flat X,Y,Z coordinate triples; unwalkable goal tiles are skipped; every goal blocked -> all-unreachable field). `obj.StartTileFlowFollow(Field, Grid, OriginTile, Speed)` walks it with the same native follower - the lists stay CALLER-OWNED, one field serves any number of movers; returns false when the mover's tile is off-grid or unreachable in the field. Fields are build-once snapshots: after `TileGridSetWalkable` edits affected movers HALT and surface in `TilePathFollowTakeBlocked()` (drains like TakeArrivals) - rebuild the field and restart them. TakeArrivals/Stop/IsDone work as for path follows.
|
|
251
|
+
- Layered grids (bridges/multi-floor): `TileGridCreate3D(TilesX, TilesY, Layers, DefaultWalkable)`; each layer is an independent walkable plane at OriginTile.z + Z. `TileGridSetWalkable/TileGridIsWalkable(Grid, X, Y, Z, ...)`, `TileGridSetLinkUp(Grid, X, Y, Z, Linked)` marks a stair cell connecting Z and Z+1 (both ends must also be walkable), `TileGridFindPath4/8(Grid, StartX, StartY, StartZ, GoalX, GoalY, GoalZ)`. On layered grids MoveAlongTilePathContinuous drives Z too (object bottom rests on the layer plane). No edge blocking on layered grids.
|
|
206
252
|
</tile_pathfinding>
|
|
207
253
|
|
|
208
254
|
<exported_params_v1>
|
|
@@ -218,7 +264,7 @@ event OnSpawned {
|
|
|
218
264
|
- `@exportRange(...)` implies export
|
|
219
265
|
- `@exportColor` implies export
|
|
220
266
|
- local `var`, `let`, and other types are not supported in v1
|
|
221
|
-
- exported params appear in the editor AI
|
|
267
|
+
- exported params appear in the editor AI Feed tune drawer when exactly one object with those vars is selected
|
|
222
268
|
- for live tuning in the editor, apply exported values inside `event OnTuneChanged { ... }`
|
|
223
269
|
- common pattern: initialize once in `OnPreSpawn`/`OnSpawned`, then mirror the same property updates in `OnTuneChanged`
|
|
224
270
|
- user keeps nudging one number per message (offset/size/speed/color) -> declare it `@exportRange`, ask them to drag the slider, then bake the settled value back into the source instead of resubmitting per nudge
|
|
@@ -234,6 +280,17 @@ event OnTuneChanged {
|
|
|
234
280
|
#end
|
|
235
281
|
</exported_params_v1>
|
|
236
282
|
|
|
283
|
+
<persistent_state_v1>
|
|
284
|
+
- `@persist var Name = ...` (class-scope top-level var): value survives save/load and world reload
|
|
285
|
+
- supported types: Number, Bool, Text, Vector, Position (others are a compile error)
|
|
286
|
+
- scene objects rehydrate by object name; a standalone `@persistSpawned` line inside a class also recreates its runtime-spawned instances (saved template + position/rotation/size + tagged vars); crowd instances and multi-object templates are not recreated
|
|
287
|
+
- class edits migrate loudly: removed/retyped tagged vars are dropped and reported; new tagged vars start at their defaults
|
|
288
|
+
- a destroyed scene object reappears on load; persist a `@persist var Opened` style flag and react in OnSpawned
|
|
289
|
+
- saving is automatic per project; there is nothing to call
|
|
290
|
+
- PersistClear() wipes the project's saved state (pair with RestartGame() for a New Game button)
|
|
291
|
+
- prefer @persist over PersistGet/SetNumber for object state; the KV remains for per-player counters
|
|
292
|
+
</persistent_state_v1>
|
|
293
|
+
|
|
237
294
|
<multiplayer_guidance>
|
|
238
295
|
- Core model: multiplayer is deterministic lockstep with client prediction/rollback, NOT object replication.
|
|
239
296
|
- All weave code, scene objects, and sim state run identically on every client/server (same world + inputs => same result; verifiable on-chain).
|
|
@@ -257,16 +314,22 @@ event OnTuneChanged {
|
|
|
257
314
|
- Validation/simulate auto-joins a second mock player a few frames in when these per-player APIs are referenced, so per-player join/visibility/index logic is actually exercised.
|
|
258
315
|
- Players/teams:
|
|
259
316
|
- SetPlayerOwnerIndex/GetPlayerOwnerIndex, SetRenderVisibleOnlyToPlayerIndex/SetRenderVisibleToAllPlayers, SetTeam/GetTeam
|
|
317
|
+
- Scene field: `object X { team = 0 }` assigns the spawn team (same values as SetTeam; omit for teamless).
|
|
318
|
+
- Fog of war: SetFogOfWarEnabled(true) (needs terrain) then SetSightRadius on teamed objects - per-team grid; shroud, explored-memory, enemy-object hiding, and pick-through are built in (no per-object SetRenderVisible needed).
|
|
260
319
|
- Iterate via GetAllPlayers()/GetAllPlayersWithTag(..) (existing players only, no None entries).
|
|
320
|
+
- A player who leaves stays in GetAllPlayers() for ~90s (reconnect window) before OnDestroy: gate rosters/win checks on p.IsConnected(), not Exists(), and react to the drop in `event OnPlayerLeft` / to a grace resume in `event OnPlayerReconnected` (both fire on that player's object). Bots are always connected.
|
|
321
|
+
- p.GetPlayerName() = the user's display name (not unique - key logic by player object/index, not name). The engine draws it above visible remote players (SetPlayerNamesVisible(false) hides that); obj.SetHoverText(text) puts a persistent label above any other body (hero units, NPCs).
|
|
261
322
|
- Never assume a fixed player count or index order (players join/leave).
|
|
262
323
|
- Determinism caveats:
|
|
263
324
|
- Time is sim-time and pause-aware: TimeSecs/TimeTicks freeze on pause and may reset on world reset, so capture your own baseline (StartTime=TimeSecs()); RawTimeSecs/RawTimeTicks is unfrozen raw time.
|
|
264
|
-
- RandomNumber/RandomPosition* are deterministic (seeded per tick + calling object + call index, so repeated or cross-object same-tick calls draw fresh values); inside player input events (key/mouse/action) all Random* calls are seeded by that input edge instead, so the predicted result matches the final one. RandomChancePercent outside input events returns false during client prediction, so there use it only for delay-tolerant side effects (e.g. drops), not moment-to-moment combat.
|
|
325
|
+
- RandomNumber/RandomPosition* are deterministic (seeded per tick + calling object + call index, so repeated or cross-object same-tick calls draw fresh values); inside player input events (key/mouse/action) all Random* calls are seeded by that input edge instead, so the predicted result matches the final one. RandomChancePercent outside input events returns false during client prediction, so there use it only for delay-tolerant side effects (e.g. drops), not moment-to-moment combat. A fresh load always starts at the same tick, so the FIRST draws repeat across sessions (identical opening deck/magazine every load) — mix a per-session value (e.g. TimeTicks() at game start) into anything that must differ between sessions.
|
|
265
326
|
- Don't rely on cross-object event ordering (OnSpawned order across objects is unspecified).
|
|
266
|
-
-
|
|
267
|
-
-
|
|
268
|
-
-
|
|
269
|
-
-
|
|
327
|
+
- Session APIs (for CUSTOM in-game browsers only - the engine ships a built-in session browser + share/visibility panel for every project; most games need no session code):
|
|
328
|
+
- AdvertiseSession()/StopAdvertisingSession() toggle whether this session is listed in the project's public session list (same switch the host's engine panel sets; last write wins; play-mode entries auto-list - a game that wants private sessions calls StopAdvertisingSession()). Unlisted sessions stay joinable via share link.
|
|
329
|
+
- ListSessions()/JoinSession are relay session discovery; identity is automatic - listings match peers running this project with identical content (same saved version). SessionStage(i)/SessionPlayerCount(i) describe each listed session.
|
|
330
|
+
- OpenSessionBrowser() opens the built-in browser from a game menu.
|
|
331
|
+
- These do NOT create replication endpoints or change sim authority; once joined, the same shared deterministic sim applies.
|
|
332
|
+
- Session UI lists real joined players via GetAllPlayers(); never simulate joins with a local counter/button.
|
|
270
333
|
- AI players (bots): var bot = SpawnAIPlayer("Rival") spawns a real PlayerObject: '#class player' code + OnSpawned run for it, it appears in GetAllPlayers(), has physics/animations, but no user/camera/UI. The Name arg becomes the bot's object name and its GetPlayerName().
|
|
271
334
|
- Drive it from script: bot.SetAIMoveVector(dir) (held stick, persists until changed) and bot.PressAIAction(Action.Jump) (tap; Down next frame, auto-release after). The bot's own ...ByThisPlayer input reads and Action events then behave as if a user pressed them.
|
|
272
335
|
- bot.IsAIPlayer() distinguishes bots; bot.DespawnAIPlayer() (or bot.Destroy()) removes them. Keyboard/mouse reads (IsKeyPressedByThisPlayer, GetMouseTarget*) return false/none for bots.
|
|
@@ -404,14 +467,18 @@ event OnSpawned {
|
|
|
404
467
|
|
|
405
468
|
<object_types>
|
|
406
469
|
- WorldObject: Game's objects, start off with static/CanCollide physics
|
|
470
|
+
- Scene `position = (x, y, z)` places the object's PIVOT; primitives and library meshes pivot at their base, so z is the bottom (like position_tiles). Effect objects render their authored local origin at `position`.
|
|
407
471
|
- PlayerObject: Object representing player
|
|
408
472
|
- RegionObject: Used in weave for detecting object overlap etc. cannot be seen in play-mode, cannot collide, or click
|
|
409
473
|
- PhysicsObject: like WorldObject, but starts as dynamic/CanCollide physics
|
|
410
474
|
- CrowdObject: Useful as creatures/walkable-NPCs. like WorldObject, but starts with crowd collision avoidance
|
|
411
|
-
- it
|
|
475
|
+
- it steers around other crowd agents and obstacles it approaches at an angle; a blocker dead-ahead STOPS it (avoidance is anti-collision, not pathfinding) - route around structures with tile pathfinding
|
|
476
|
+
- RTS units: route with `MoveAlongTilePathContinuous` on a `crowd` receiver - the path sets the preferred velocity and avoidance shapes it every frame (global path + local separation). The zero-call `StartTilePathFollow` is waypoint-granular, so avoidance cannot shape it mid-segment - use it for overlap-tolerant swarms instead. Terrain cliffs and tilemap walls are invisible to avoidance until `TileGridBakeCrowdObstacles` bakes them from the nav grid.
|
|
412
477
|
- Avoidance scope: XY-plane only; Z/height is ignored.
|
|
478
|
+
- Gravity is ON at spawn; crowd bodies stand on static geometry, terrain, and tilemaps (not on dynamic objects). ClimbingCrowdObject remains the climb/stack-capable crowd.
|
|
413
479
|
- For grid-based or fixed-lane games (e.g., Tower Defense), avoid using CrowdObject, as its built-in avoidance can cause objects to move off their intended path.
|
|
414
480
|
- Prefer it over plain WorldObject for free-roaming monster swarms in top-down games.
|
|
481
|
+
- Bone/pose simcalls (SetBoneRotation family, SetUpperBodyAnimationWeight, SetRenderOffset) reject crowd instances - use a regular object for featured NPCs needing bone-level control.
|
|
415
482
|
- ClimbingCrowdObject: like CrowdObject, but simulated by the deterministic "climbing crowd" step (lean crowd bodies)
|
|
416
483
|
- Prefer it over plain WorldObject for player-chasing enemies in flat first-/third-person worlds.
|
|
417
484
|
- Always seeks the nearest player in XY each tick; players are never pushed (climbers always yield)
|
|
@@ -419,13 +486,15 @@ event OnSpawned {
|
|
|
419
486
|
- Terrain + static primitive obstacles; supports elevator-like climbing
|
|
420
487
|
- Can climb/stack on other climbers; large swarms may pile into a single moving blob
|
|
421
488
|
- DisplayObject: like WorldObject but visual-only — no collision, no interaction events (Touched, ClickThisObject, etc. will never fire), doesn’t respond to forces
|
|
489
|
+
- When a display shell forms a player-reachable structure (building, wall), add a static collision core or invisible collider proxy behind it — display-only geometry is walked through.
|
|
422
490
|
- EffectObject: visual effects
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
-
|
|
426
|
-
-
|
|
427
|
-
-
|
|
491
|
+
- pointLight: local point light. `pointLight L { position = (0, 0, 150) radius = 500 intensity = 4 color = (1, 0.85, 0.6) }` - radius cm (default 100 = dim 1m pool); optional cast_shadow = true, light_diffuse_scale/light_specular_scale. For a bulb on a mesh prop, prefer an emitter(...) socket in the #mesh (see <mesh_dsl_usage>)
|
|
492
|
+
- spotLight: cone along the object's +X facing (aim with rotation); pointLight keys plus cos_inner/cos_outer = cosine of the half-angle, not degrees (defaults 24deg/30deg)
|
|
493
|
+
- diffuseOnlyLight: diffuse-only, flat core with fade_width (cm) fade shell; pointLight keys minus cast_shadow/light_specular_scale
|
|
494
|
+
- areaLight: area light; pointLight keys minus cast_shadow
|
|
495
|
+
- directionalLight: the world sun (editor-placed); control it with the SetSun* simcalls, not the generic light setters
|
|
428
496
|
- PositionMarkerObject: position marker object useful as things like waypoints (easily manipulated by human visually in editor)
|
|
497
|
+
- DecalObject: projected surface overlay re-writing color/normal/roughness on the geometry inside its oriented box (bullet holes, stains, scorch marks). Spawn at runtime with SpawnDecal(Material, Position, RotationEuler, Size); scene keyword `decal Name { material = "M" ... }`. Globals: SetAllDecalsEnabled, SetDecalCullingDistanceM, SetAssetSubmeshReceiveDecal
|
|
429
498
|
</object_types>
|
|
430
499
|
|
|
431
500
|
<variable_types>
|
|
@@ -437,9 +506,11 @@ event OnSpawned {
|
|
|
437
506
|
- Vector — (X,Y,Z) direction/offset; also used for RGB triples.
|
|
438
507
|
- Position — world point (X,Y,Z). Alias of Vector; implicit cast both ways.
|
|
439
508
|
- List<T> — homogeneous typed list; T is Number | Bool | Text | Object | Vector | Position | Timer | a class name (e.g. 'var Waypoints: List<Position>', 'var Minions: List<Monster>'). In List.* signatures below, T is the element type.
|
|
440
|
-
-
|
|
509
|
+
- 'var X: List<T>' needs no initializer (class scope or local); lists start empty.
|
|
441
510
|
- Bare 'var X: List' infers T from usage; annotate List<T> explicitly when inference reports conflicting evidence.
|
|
442
|
-
- No List<List<T
|
|
511
|
+
- No List<List<T>>. Non-empty literals ([1,2,3]) are const-only: runtime lists start empty then Add, or Resize(n, default).
|
|
512
|
+
- Class-scope 'const Ids = [10, 20, 30]' (Number/Bool/Text/Vector elements, type inferred) bakes a read-only table into the program: Get/Count/Contains/for-each work, writes are runtime errors. Const tables have zero per-frame snapshot/hash cost — item/monster/config databases belong here, not in runtime lists filled row-by-row at startup.
|
|
513
|
+
- Map<Number, T> — keyed store (T: Number | Bool | Text | Object): Set(K, V), Get(K) (missing key is a runtime error), GetOr(K, Default), Has(K), Remove(K)->Bool, Count(), IsEmpty(), Clear(). Keys compare exactly (no fractional truncation) — use integer keys. Replaces hand-rolled bucket/chain index lists for id -> row lookups (entity id -> row, structure id -> data).
|
|
443
514
|
- Contains/IndexOf: List<Number|Bool|Vector|Position> and object lists only (not List<Text>/List<Timer>).
|
|
444
515
|
- Sort()/SortDescending(): in-place numeric sort, List<Number> only.
|
|
445
516
|
- List<struct>.SortByField("FieldName")/SortByFieldDescending: in-place sort of a record list by one of its Number fields (field name is a compile-checked string literal). Ties keep original order.
|
|
@@ -453,17 +524,19 @@ event OnSpawned {
|
|
|
453
524
|
}
|
|
454
525
|
- Fields: Number | Bool | Vector | Position | Object (plain or class-typed, e.g. 'var M: Monster' — member calls through class-typed fields are validated statically). Max 16 slots; Vector/Position count as 3, Object counts as 1. No Text/Timer/nested-struct fields yet. Object fields default to None (zeroed class-scope structs and Resize-grown list elements included).
|
|
455
526
|
- Construct positionally with one arg per field: 'var S = Stat(100, Vector(1,0,0))'. Read/write fields ('S.Health += 1', 'S.Speed.X = 2') on locals, Self vars, and list elements ('Table.Get(i).Health = 5' writes back); copies are by value ('var C = S' snapshots), so fields on a function's returned struct need a var first.
|
|
456
|
-
-
|
|
527
|
+
- 'var Stats: Stat' without an initializer starts zeroed in any scope, Object fields None (no struct literals at class scope; fill in OnSpawned). Structs work as function params/returns.
|
|
457
528
|
- List<struct> is supported ('var Table: List<Stat>') with Add/Get/Set/RemoveAt/Swap/Shuffle/Resize(count)/Count/IsEmpty/Clear + for-each; not Contains/IndexOf/Pop/Last/GetRandomElement or Resize with fill.
|
|
458
529
|
- Lists cap at ~1M elements: an every-frame Add with no Clear/dedupe (e.g. selections, hit logs) reaches the cap in minutes of play and stops the world with CapacityExceeded - clear or bound accumulating lists.
|
|
459
530
|
- No struct operators ('==' included) — compare fields; no scene-clause overrides.
|
|
460
531
|
|
|
461
532
|
Rules:
|
|
462
533
|
- The literal None is valid **only** for Object (e.g., 'var o:Object = None').
|
|
534
|
+
- An annotated 'var' may omit its initializer in any scope and starts at the type default: 0 / false / "" / zero vector / None / done Timer / empty List / zeroed struct. Untyped locals still need '= value'.
|
|
535
|
+
- 'const Name = <value>' (class scope or local) folds at compile time with zero snapshot cost; values that never change (tuning numbers, names, data tables) belong in const, not var.
|
|
463
536
|
</variable_types>
|
|
464
537
|
|
|
465
538
|
<tags>
|
|
466
|
-
- Tag is like enum, and can be made up by user without any declaration
|
|
539
|
+
- Tag is like enum, and can be made up by user without any declaration. `Tag.None` is not writable in code; a Tag param always filters to that tag — there is no empty-tag value, so for an unfiltered query use the overload without a Tag param.
|
|
467
540
|
- Use any made-up name (e.g. Tag.Monster) across classes; the same name resolves to the same tag everywhere.
|
|
468
541
|
- Assign a tag directly in world description: object MySpawn { tag = Tag.Spawn }
|
|
469
542
|
- AddTag/RemoveTag add and clear one tag; ClearTag() wipes all tags. Tag membership can be toggled at runtime (e.g. flag/unflag Tag.Burning) and HasTag/GetAllObjectsWithTag update immediately.
|
|
@@ -491,11 +564,13 @@ object test : MeleeEnemy { size = (70,70,100), override MaxHP = 100 }
|
|
|
491
564
|
<world_description_information>
|
|
492
565
|
- 1 world unit = 1 cm (centimeter)
|
|
493
566
|
- Basic cube is 100x100x100 cm, sphere 100 cm diameter
|
|
494
|
-
- Basic models: cube, sphere, cylinder, capsule, cone (apex +Z), wedge (slope rises toward +X)
|
|
567
|
+
- Basic models: cube, sphere, cylinder, capsule (long axis +Z), cone (apex +Z), wedge (slope rises toward +X)
|
|
495
568
|
- Object position is the BOTTOM center: the object rests its base at position.z (top = position.z + size.z). A ground slab with its top at z=0 is position = (0, 0, -H) size = (X, Y, H).
|
|
496
569
|
- Object size is clamped per axis: 50,000 cm for solid objects, 100,000 cm display-only (10,000 cm dynamic). Meshes scale to object size, so oversized objects render SQUASHED — tile large ground/water/road geometry into pieces under the cap.
|
|
497
570
|
- cone/wedge collide as boxes. Walkable ramp = rotated thin cube, e.g. size = (630, 300, 20) rotation = (0, -18, 0) rises toward +X
|
|
498
|
-
- Marketplace/#mesh models also collide as their AABB box: a boat deck, bridge, or house interior is NOT walkable/enterable by default. Declare `collision = mesh` on the object clause (object Boat { model = "..." collision = mesh }) to collide against the model's actual geometry. Skinned/animated models keep the box.
|
|
571
|
+
- Marketplace/#mesh models also collide as their AABB box: a boat deck, bridge, or house interior is NOT walkable/enterable by default, and interior furniture's box juts far past its real shape, blocking walkways. Declare `collision = mesh` on the object clause (object Boat { model = "..." collision = mesh }) to collide against the model's actual geometry — the right choice for enterable structures AND for furniture players move around. Skinned/animated models keep the box.
|
|
572
|
+
- `collision = off` spawns the object with no collider: nothing blocks on it and Touched/Click never fire (detect pickups by distance) — for movers/markers that must not block. Purely decorative geometry is cheaper as a `display` clause.
|
|
573
|
+
- Colliders are also the hit/UI anchor: Touched/Click/raycasts test the collider box, and hover bars/labels draw on the object they were set on. Size colliders to the visible model and set bars on the visible object, not a hidden collider proxy (symptoms: shots that visually touch but miss; bars floating beside the model).
|
|
499
574
|
- Player is 40 cm diameter and 180 cm height, to fit inside a 100x100 cm sized tile with gap
|
|
500
575
|
- Default player spawn: Origin() (0,0,0).
|
|
501
576
|
- Calling an Object function without a caller implies 'Self.'
|
|
@@ -535,6 +610,7 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
|
|
|
535
610
|
- Dynamic objects have gravity on by default
|
|
536
611
|
- Dynamic bodies have no feel knobs (no bounciness/friction/mass calls): use them for incidental 3D physics - debris, tumbling props, knockback - where the exact trajectory doesn't matter. Don't use them for 2D-style games.
|
|
537
612
|
- For gameplay-tuned motion (sport balls, vehicles, designed arcs: basketball, tennis, racing), keep the object kinematic and integrate your own velocity var per frame (V.Z = V.Z - g*DeltaSecs(); MoveWithVelocityContinuously(V); reflect/steer V on contact - see #class Ball in <example_breakout>). Works because plain kinematic objects have gravity OFF; a gravity-affected character discards scripted Z (see <movement_rules>). Hand-rolled constants stay user-tweakable (@exportRange).
|
|
613
|
+
- Designed arcs that must land on an exact point at an exact time (serves, passes, lobs, throws): ArcToTarget/ArcToTargetWithPeak build the arc once (gravity captured with it), ArcFlightSeconds gives the contact time, SetPosition(ArcPositionAt(arc, t)) drives the object each frame - closed form, so no drift and no velocity kink between rally phases. Feed the remaining time to MoveToArriveIn and PlayAnimationHittingContactIn so mover and clip land on the same instant. Do not hand-author per-phase LerpVector segments.
|
|
538
614
|
- if you move Static object with MoveTo etc., it becomes Kinematic (behaves like Kinematic platform)
|
|
539
615
|
</defaults>
|
|
540
616
|
|
|
@@ -560,7 +636,7 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
|
|
|
560
636
|
<defaults>
|
|
561
637
|
- PlayerObject defaults to kinematic with gravity
|
|
562
638
|
- `collision = mesh` objects keep mesh collision while static or moved as a kinematic platform (players ride them); giving one gravity or PreventOverlap drops it back to box collision.
|
|
563
|
-
- SetCharacterPreventOverlap
|
|
639
|
+
- SetCharacterPreventOverlap(true) gives a kinematic object fake-physics separation from CanCollide objects and supplies a gravity character's ground support: off on a player with gravity = falls through the floor
|
|
564
640
|
- SetCharacterOverlapIgnoreTag(Tag, IsOn) makes this character's overlap resolution skip objects with that tag (selective ghosting, e.g. walk through Tag.Enemy while charging) without touching ground collision; Touched/regions still fire. Prefer it over toggling SetCharacterPreventOverlap or SetCanCollide for pass-through-some-objects behavior.
|
|
565
641
|
</defaults>
|
|
566
642
|
|
|
@@ -576,8 +652,11 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
|
|
|
576
652
|
- MoveTowardContinuousFacing
|
|
577
653
|
- MoveTowardContinuousHorizontal
|
|
578
654
|
- MoveTowardContinuousHorizontalFacing
|
|
655
|
+
- MoveToArriveIn (speed = remaining distance / remaining seconds; pair with ArcFlightSeconds)
|
|
656
|
+
- MoveToArriveInFacing
|
|
579
657
|
</kinematic_functions>
|
|
580
|
-
-
|
|
658
|
+
- These continuous movers drive ONE frame of velocity then stop: call them every frame (a brain loop that Waits 0.2s between calls crawls at ~1/12 speed).
|
|
659
|
+
- On gravity-affected characters (the PlayerObject default; EnableCharacterGravity(true) objects) the Z component of these calls is silently DISCARDED - gravity/jump owns vertical velocity, only X/Y apply, and the mover never lifts. Flying/hovering: EnableCharacterGravity(false) (players: SetPlayerModeHover); jumps: Jump(). Gravity-off and bodyless objects honor full 3D velocity.
|
|
581
660
|
- Gravity characters auto-step rises up to 45cm (capped at 1/4 body height for small NPCs); taller needs a ramp or Jump()
|
|
582
661
|
- To move somewhere over N seconds, use an explicit per-frame loop instead of a fire-and-forget helper:
|
|
583
662
|
<example_of_move_over_time>
|
|
@@ -641,19 +720,20 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
|
|
|
641
720
|
if T >= 1 { O.SetPosition(Target) break }
|
|
642
721
|
O.SetPosition(LerpVector(Start, Target, EaseInOut(T)))
|
|
643
722
|
}
|
|
644
|
-
- SetPosition/SetCenterPosition/SetRotation are continuous "moved" setters: they keep velocity, contacts, and render interpolation, so they are the right per-frame drivers for tweens, movers, and platforms (bodiless
|
|
723
|
+
- SetPosition/SetCenterPosition/SetRotation are continuous "moved" setters: they keep velocity, contacts, and render interpolation, so they are the right per-frame drivers for tweens, movers, and platforms (bodiless or kinematic objects; a colliding STATIC re-carves its collider on every set - drive it with Move* movers instead, which auto-convert it to kinematic). On force-affected dynamic bodies they fight physics integration (warned in the log) - use SetPhysicsVelocity/AddImpulse there, or SetAffectedByForce(false) first.
|
|
645
724
|
- TeleportTo is a discontinuity: it zeroes velocity and snaps interpolation. Use it for instant far moves, not per-frame motion.
|
|
646
725
|
- EaseOutBack overshoots ~10% past the target near the end (snappy UI pops); EaseOutBounce settles with bounces. Both still end at exactly T=1 -> 1.
|
|
647
726
|
</easing>
|
|
648
727
|
|
|
649
728
|
<world_markers>
|
|
650
729
|
- World-anchored HUD markers (lock-on reticles, waypoints, offscreen-target indicators, icon nameplates) are screen-space UI driven by projection. Do NOT build them from thin world cubes or hand-aimed meshes.
|
|
651
|
-
- Recipe: a canvas image element + WorldToScreenPosition (call on a PlayerObject; returns base UI viewport coords 1600x900, or Vector(-1,-1,0) when behind/unprojectable) + SetCenterPosition per frame:
|
|
730
|
+
- Recipe: a canvas image element + WorldToScreenPosition (call on a PlayerObject; returns base UI viewport coords 1600x900, or Vector(-1,-1,0) when behind/unprojectable) + SetCenterPosition per frame. Call Marker.SetUiPivot(1, 1) ONCE first: without it, SetPosition/SetCenterPosition both place the element's TOP-LEFT at the point, so a 320x220 marker lands half its size off. The marker must be a direct child of a plain frame/canvas — inside a vStack/hStack/grid the parent's layout wins and the position is ignored.
|
|
731
|
+
Marker.SetUiPivot(1, 1) // once, e.g. OnSpawned
|
|
652
732
|
var sp = WorldToScreenPosition(Target.GetCenterPosition())
|
|
653
|
-
if sp.X >= 0 { Marker.SetVisible(true) Marker.SetCenterPosition(sp) } else { Marker.SetVisible(false) }
|
|
733
|
+
if sp.X >= 0 { Marker.SetVisible(true); Marker.SetCenterPosition(sp) } else { Marker.SetVisible(false) }
|
|
654
734
|
- Per-player markers (each player sees their own lock-on): put the marker on a SpawnCanvas HUD owned via SetPlayerOwnerIndex and run the loop in '#class player' (see <multiplayer_guidance>).
|
|
655
735
|
- Offscreen indicator: when the sentinel fires or sp leaves 0..1600 / 0..900, clamp to the screen edge yourself (direction from screen center 800,450 toward sp).
|
|
656
|
-
- Before hand-rolling: SetHoverBar (bars/pills above objects, custom material supported),
|
|
736
|
+
- Before hand-rolling: SetHoverBar (bars/pills above objects, custom material supported), Say/SayTo (speech bubbles above the speaker), and SpawnFloatingText already cover the common above-object UI.
|
|
657
737
|
</world_markers>
|
|
658
738
|
|
|
659
739
|
<movement_laws>
|
|
@@ -661,10 +741,11 @@ Movement Laws (single-frame vs over-time)
|
|
|
661
741
|
- Exactly one continuous translate per object per frame (and one continuous rotation). Calls whose names end with Continuous/Continuously are single-frame. If multiple are issued in one frame, last call wins (later calls overwrite earlier ones). Compose motion into one call.
|
|
662
742
|
- See <time> for over-time/continuous rules.
|
|
663
743
|
- TeleportTo* is absolute this frame. If a TeleportTo* and any continuous move happen in the same frame, teleport cancels motion and sets the final transform.
|
|
664
|
-
- SnapToGround only when setting up an initial position; TeleportTo only when instantly moving far away (it zeroes velocity and snaps interpolation). For authored per-frame motion (tweens/movers) use SetPosition, which keeps velocity and interpolation; see <easing>.
|
|
744
|
+
- SnapToGround only when setting up an initial position; TeleportTo only when instantly moving far away (it zeroes velocity and snaps interpolation). For authored per-frame motion (tweens/movers) on bodiless/kinematic objects use SetPosition, which keeps velocity and interpolation; see <easing>.
|
|
745
|
+
- Arrival at an OBJECT: colliders stop the mover before centers meet, so DistanceTo(target) may never drop below both half-extents (reads as a freeze while Move* keeps firing). Gate on SurfaceGapTo(Target) <= margin instead.
|
|
665
746
|
- Timing & readback: see <frame_and_scheduling_model> for tick order and GetPosition/GetRotation semantics.
|
|
666
747
|
|
|
667
|
-
- Clarification: SnapToGround raycasts downward (-Z) from the object's bottom-center and snaps to the first WorldObject hit (ignores PhysicsObject/PlayerObject/CrowdObject etc.). It overrides any Z offset you set earlier in the same frame. For stacking on tiles/objects, compute Z = baseZ + baseSizeZ/2 + itemSizeZ/2 instead of using SnapToGround.
|
|
748
|
+
- Clarification: SnapToGround raycasts downward (-Z) from the object's bottom-center and snaps to the first WorldObject hit (ignores PhysicsObject/PlayerObject/CrowdObject etc.). It overrides any Z offset you set earlier in the same frame. For stacking on tiles/objects, compute Z = baseZ + baseSizeZ/2 + itemSizeZ/2 instead of using SnapToGround. Flat/plane items (rugs, road markings, decals): add ~1cm more — face-on-face at the same height shimmers (z-fights), worse at long view distances.
|
|
668
749
|
</movement_laws>
|
|
669
750
|
|
|
670
751
|
<inputs>
|
|
@@ -672,7 +753,7 @@ Movement Laws (single-frame vs over-time)
|
|
|
672
753
|
- Builtin action constants: Action.Primary, Action.Secondary, Action.Interact, Action.Jump.
|
|
673
754
|
- Custom actions (Weave2 worlds): declare a top-level scene clause `action dash { label="Dash" key=Key.LeftShift icon="image.<owner>.<name>" }` (all properties optional; icon is a QUOTED image url). The name then works everywhere builtins do: Action.dash, IsActionPressedByThisPlayer(Action.dash), event ActionDownByThisPlayer[dash]. Prefer custom actions over raw IsKeyPressed for gameplay verbs: they get a mobile touch button (label text) automatically when the world handles their events, and `key=` gives the desktop binding. Up to 27 per world; a key already bound (or used by another action) is a load error. Undeclared action names are a compile error.
|
|
674
755
|
- PlayerObject.IsActionPressedByThisPlayer(Action.Primary) .. must be in player class; checks this player's held action.
|
|
675
|
-
- PlayerObject.GetMoveVectorWorld() / GetMoveVectorCamera() .. this player's normalized move vector; diagonals clamp to length 1, thumbstick magnitudes below 1 are preserved.
|
|
756
|
+
- PlayerObject.GetMoveVectorWorld() / GetMoveVectorCamera() .. this player's normalized move vector; diagonals clamp to length 1, thumbstick magnitudes below 1 are preserved. World = raw input on fixed world axes (W = +Y), camera-independent; Camera = rotated by the player's orbit yaw, which per-frame camera overrides FREEZE - under SetCamera*ThisFrame, derive steering from your authored camera yaw instead.
|
|
676
757
|
- event ActionDownByThisPlayer[Primary] / ActionUpByThisPlayer[Primary] .. must be in player class; options are the builtins plus declared action names.
|
|
677
758
|
- Current builtin bindings: Left mouse -> Primary, Right mouse -> Secondary, E -> Interact, Space -> Jump.
|
|
678
759
|
- Current movement bindings: WASD and mobile thumbstick feed the shared move vector.
|
|
@@ -691,6 +772,7 @@ Movement Laws (single-frame vs over-time)
|
|
|
691
772
|
- Binding P in a Keyboard event overrides the built-in P sim-pause in game mode (editor P still pauses)
|
|
692
773
|
- Delivery: ALL Weave input events (keyboard, mouse, action) fire in GAME mode only - editor mode delivers none of them (use IsKeyPressedByThisPlayerInEditor for editor tooling). Global KeyboardDown[..] reaches only live objects of the handler's class and skips objects currently selected in the editor (selection survives pressing Play) - put input handlers in `#class player`, where ByThisPlayer/Action events always land.
|
|
693
774
|
- AI bots (SpawnAIPlayer) have NO keyboard at all and mobile sends only WASD + touch buttons: actions are the only input verb that works for every participant. Bare IsKeyPressed / KeyboardDown[..] are any-player and always false for bots/mobile.
|
|
775
|
+
- Hold-to-drain abilities (turbo/sprint): do not gate on `IsKeyPressedByThisPlayer(..) and Fuel > 0` — with passive regen that re-triggers every frame at empty and strobes FX/sound. Keep an Armed flag: disarm when fuel hits 0; re-arm only in KeyboardDownByThisPlayer (or the action's press event) after release + cooldown.
|
|
694
776
|
- Object.GetMouseTargetPosition (caller must be a PlayerObject) returns a far point along the mouse ray when nothing is hit; use GetMouseTargetObject to test what was hit
|
|
695
777
|
- Mobile/touch: pinch in/out maps to mouse scroll (event OnMouseScroll)
|
|
696
778
|
</inputs>
|
|
@@ -717,34 +799,34 @@ Movement Laws (single-frame vs over-time)
|
|
|
717
799
|
<colors>
|
|
718
800
|
- colors are Vector rgb 0..1, e.g. SetColor(Vector(1, 0.5, 0)); ColorHex(Hex:Text) parses hex text like #FF8000
|
|
719
801
|
- Default daylight leans slightly cool (sky ambient), most visibly in shadows - that is the intended look; author materials in their intended base colors rather than counter-tinting them for it.
|
|
720
|
-
- Scene grade
|
|
802
|
+
- Scene lighting/grade is user-tuned in the Adjust panel (see <adjuster_usage>): to fix one object's color, recolor that object/material - never with scene-wide grade.
|
|
721
803
|
</colors>
|
|
722
804
|
|
|
723
805
|
<time>
|
|
724
|
-
- Wait(1) .. Wait 1 sec
|
|
806
|
+
- Wait(1) .. Wait 1 sec (event bodies only; not inside custom functions)
|
|
725
807
|
- forever { ... } runs its scope once per frame (implicit WaitNextFrame() between iterations)
|
|
726
808
|
- doesn't need WaitNextFrame() to work
|
|
727
809
|
- use Wait(seconds) inside forever if you want slower than once-per-frame
|
|
728
810
|
- while { ... } is same-frame (no implicit yield). Use only for bounded work that must finish this frame. For per-frame logic/conditions, prefer forever + if.
|
|
729
|
-
-
|
|
730
|
-
|
|
731
|
-
- Clarification: "Over-time" functions (multi-frame, e.g. Wait, WaitNextFrame) run across multiple frames. These are NOT allowed inside funcs; use them in events instead.
|
|
811
|
+
- A same-frame loop stuck on movement/physics/time (e.g. while GetPosition().DistanceTo(t) > r { MoveTowardContinuous.. }) trips the SameFrameLoopBudgetExceeded guard - use forever + if, or Wait in the loop.
|
|
812
|
+
- Over-time calls (Wait, WaitNextFrame) work only in event bodies, never inside funcs. Continuous* calls are single-frame (call every frame you want the effect) and are fine inside funcs.
|
|
732
813
|
</time>
|
|
733
814
|
|
|
734
815
|
<camera>
|
|
735
816
|
- default camera is third person with mouse-look (like fortnite).. mouse target will just be crosshair target
|
|
736
|
-
- SetIsMouseLookOn(true) is the default mouse look like counter-strike and fortnite (SetCameraToFirstPerson also turns it on): the cursor is hidden and pinned to screen center, so players CANNOT click UI buttons — call
|
|
737
|
-
- Camera always follows PlayerObject every frame unless overridden by SetCameraThisFrame()/SetCameraLookAtThisFrame()/SetChaseCameraThisFrame() (each is a one-frame override; call every frame to keep it).
|
|
817
|
+
- SetIsMouseLookOn(true) is the default mouse look like counter-strike and fortnite (SetCameraToFirstPerson also turns it on): the cursor is hidden and pinned to screen center, so players CANNOT click UI buttons — call SetCursorUiMode(true) while a menu/shop/end screen is open (releases look AND shows the cursor), SetCursorUiMode(false) on close (touch players can still tap). Spectator + per-frame camera overrides (SetCameraLookAtThisFrame) keep the cursor pinned+hidden while the script owns the camera angles: commander-camera/RTS worlds call SetCursorUiMode(true) once at player spawn
|
|
818
|
+
- Camera always follows PlayerObject every frame unless overridden by SetCameraThisFrame()/SetCameraLookAtThisFrame()/SetChaseCameraThisFrame() (each is a one-frame override; call every frame to keep it) — or parked with SetCameraFixed(Position, LookAt), the persistent one-call variant. While a per-frame override or fixed camera is active the mouse no longer steers the camera. The camera always views the caller's own PlayerObject; no simcall views through another player's object.
|
|
819
|
+
- SetCameraFixed(Position, LookAt) parks the camera (security cam / lobby / cinematic shot): persists across frames AND save/reload, camera collision is off while fixed, mouse-look is ignored; any camera-mode switch (SetCameraToThirdPerson(...) etc.) releases it — SetIsMouseLookOn/SetCursorUiMode do NOT. Prefer it over a per-frame SetCameraLookAtThisFrame loop for static shots.
|
|
738
820
|
- Default camera yaw/rotation is 0 degrees.
|
|
739
821
|
- At camera yaw 0: camera forward is +X, camera up is +Z, and camera left is +Y.
|
|
740
822
|
- Camera yaw is right-hand (same as object yaw): `SetCameraRotation(0)` faces +X, `SetCameraRotation(90)` faces +Y, `SetCameraRotation(-90)` faces -Y.
|
|
741
823
|
- Objects also face +X at rotation 0, with +Y their LEFT side (right-hand system): a character's right-side parts sit at negative y.
|
|
742
|
-
- Angles in this API are degrees.
|
|
824
|
+
- Angles in this API are degrees. Trig is Number methods on the angle: `Angle.SinDegree()`/`CosDegree()`/`TanDegree()`, `Y.Atan2Degree(X)` (degrees); `.Sin()`/`.Cos()`/`.Tan()`/`.Atan2(X)` are radians. There are no global trig functions.
|
|
743
825
|
- SetCameraToTopDown() defaults to 70-degree tilt down, 900 units zoom, facing +Y.
|
|
744
|
-
- Camera-mode switch signatures: `SetCameraToTopDown()`, `SetCameraToFirstPerson()`, `SetCameraToSideView()` take no args; only `SetCameraToThirdPerson(IsMouseLookOn)` takes one Bool.
|
|
826
|
+
- Camera-mode switch signatures: `SetCameraToTopDown()`, `SetCameraToFirstPerson()`, `SetCameraToSideView()` take no args; only `SetCameraToThirdPerson(IsMouseLookOn)` takes one Bool. Switches restore collision+gravity, except an active SetPlayerModeHover/Spectator survives (SetPlayerModeGrounded exits it).
|
|
745
827
|
- 2D-style games (top-down or side-view camera) are still 3D worlds: keep a ground/background object so the camera never shows bare skybox, and for side-view keep gameplay on a constant Y plane. A compact ~500x500 cm play area suits board/arcade layouts (chess, brick-breaker).
|
|
746
828
|
- Third-person body faces its MOVEMENT direction, not the camera; GetCrosshairDirection() is camera-only. So a gun/item aimed via crosshair won't match the body when looking around while standing still. Fix: SetOrientingWithCameraYaw(true) makes the body track the camera yaw.
|
|
747
|
-
- SetCameraZoom .. 0 will make it first person, 900 will make it third-person with camera 900 units away from character
|
|
829
|
+
- SetCameraZoom .. 0 will make it first person, 900 will make it third-person with camera 900 units away from character (zoom alone never enables mouse steering; use SetCameraToFirstPerson() for playable first person)
|
|
748
830
|
- Third-person default framing: zoom 525 with pivot offset (0, -39, 87) = over-the-shoulder, body slightly left of screen center; override via SetCameraZoom()/SetCameraPivotOffset()
|
|
749
831
|
- player movement must come from the player class: either SetDefaultPlayerControlsEnabled(true) in OnSpawned (attaches the built-in default controls, equivalent to the class below) or a coded movement loop — with neither, the player and thus the camera cannot move in play-mode. SetDefaultPlayerControlsEnabled(false) detaches them again (cutscene/stun freeze).
|
|
750
832
|
- built-in default player controls (also the template for custom movement):
|
|
@@ -784,9 +866,6 @@ event OnSpawned {
|
|
|
784
866
|
- player object always stand upright (lock rotation axis to z)
|
|
785
867
|
</camera>
|
|
786
868
|
|
|
787
|
-
<built_in_functions>
|
|
788
|
-
- example: ObjectA.LookAt(ObjectB.GetPosition())
|
|
789
|
-
</built_in_functions>
|
|
790
869
|
<built_in_function_list>
|
|
791
870
|
PrintNumber(Number: Number)
|
|
792
871
|
Print(Text: Text)
|
|
@@ -800,8 +879,8 @@ SetAutoGameLogOn()
|
|
|
800
879
|
PlayerPrint(PlayerIndex: Number, Text: Text)
|
|
801
880
|
SetSunIntensity(Intensity: Number) // Default: 4
|
|
802
881
|
SetSunSpecularScale(Scale: Number) // Default: 1
|
|
803
|
-
SetSunRotation(Rotation: Number) // Default: 10
|
|
804
|
-
SetSunTilt(Tilt: Number) // Default: 46
|
|
882
|
+
SetSunRotation(Rotation: Number) // Default: 10. Azimuth, degrees from +X toward +Y; shadows fall toward Rotation+180
|
|
883
|
+
SetSunTilt(Tilt: Number) // Default: 46. Elevation above the horizon, degrees (90 = overhead)
|
|
805
884
|
SetSkybox(Skybox: Object)
|
|
806
885
|
SetSkyboxHorizonTilt(AngleDegrees: Number) // Default: 0
|
|
807
886
|
SetSkyboxRotation(RotationDegrees: Number) // Default: 0
|
|
@@ -831,11 +910,10 @@ SetShadowDepthBiasConstant(Units: Number) // Default: 1
|
|
|
831
910
|
SetShadowDepthBiasSlopeScale(Scale: Number) // Default: 2.5
|
|
832
911
|
SetShadowOpacity(Opacity01: Number) // Default: 1
|
|
833
912
|
SetStaticLodTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Default: 0.18, 0.07, 0.025
|
|
834
|
-
SetStaticLodShadowTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Default: 0.35, 0.18, 0.08
|
|
835
|
-
|
|
836
|
-
SetContrast(Contrast: Number) // Contrast multiplier around mid-gray. Default: 1 = neutral; 0 = flat gray, 2 = double
|
|
913
|
+
SetStaticLodShadowTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Same screen-size units as SetStaticLodTransitions, but for meshes drawn into the sun-shadow maps (defaults higher: shadows tolerate earlier LOD). Lower toward the view values if a large caster's shadow silhouette looks low-poly or pops. Default: 0.35, 0.18, 0.08
|
|
914
|
+
SetContrast(Contrast: Number) // Power curve around mid-gray, pre-tonemap: shadows compress, never crush to black. Default: 1 = neutral; 0 = flat gray, 2 = strong
|
|
837
915
|
SetSaturation(Saturation: Number) // Saturation multiplier. Default: 1 = neutral; 0 = grayscale, 2 = vivid
|
|
838
|
-
SetExposure(Exposure: Number) // Default: 0
|
|
916
|
+
SetExposure(Exposure: Number) // EV stops, pre-tonemap: +1 = twice as bright, -1 = half. Default: 0
|
|
839
917
|
SetShadowLift(Amount: Number) // Default: 2; 0 = off
|
|
840
918
|
SetShadowLiftThreshold(Threshold: Number) // Default: 0.06
|
|
841
919
|
SetSmaaEdgesThreshold(Threshold01: Number) // Default: 0.01
|
|
@@ -852,12 +930,12 @@ SetAllLocalShadowsEnabled(Enabled: Bool)
|
|
|
852
930
|
SetAllDecalsEnabled(Enabled: Bool)
|
|
853
931
|
SetGravity(Gravity: Number)
|
|
854
932
|
SetTerminalVelocityZ(MaxFallSpeed: Number)
|
|
855
|
-
SetFogStartDistance(StartDistance: Number) // Default: 10. The skybox is fogged only via SetFogSkyboxAffect or height fog; underwater, automatic murk overrides all fog settings
|
|
856
|
-
SetFogEndDistance(EndDistance: Number) // Default: 600
|
|
933
|
+
SetFogStartDistance(StartDistance: Number) // Default: 10. Meters (not cm). The skybox is fogged only via SetFogSkyboxAffect or height fog; underwater, automatic murk overrides all fog settings
|
|
934
|
+
SetFogEndDistance(EndDistance: Number) // Default: 600. Meters
|
|
857
935
|
SetHeightFogEnabled(Enabled: Bool)
|
|
858
|
-
SetHeightFogTop(Top: Number) // Default:
|
|
859
|
-
SetHeightFogBottom(Bottom: Number) // Default: 0
|
|
860
|
-
SetHeightFogSoftness(Softness: Number) // Default:
|
|
936
|
+
SetHeightFogTop(Top: Number) // Default: 50. Meters
|
|
937
|
+
SetHeightFogBottom(Bottom: Number) // Default: 0. Meters
|
|
938
|
+
SetHeightFogSoftness(Softness: Number) // Default: 1
|
|
861
939
|
SetGpuCullingDistance(DistanceMeters: Number) // Default: 0 (distance culling off)
|
|
862
940
|
SetFrustumCulling(Enabled: Bool)
|
|
863
941
|
SetFrustumCullingMargin(MarginMeters: Number) // Default: 0
|
|
@@ -867,12 +945,17 @@ SetGrassTerrainActiveDistanceM(DistanceMeters: Number) // Default: 140
|
|
|
867
945
|
SetGrassCullDistanceM(DistanceMeters: Number) // Default: 140
|
|
868
946
|
SetGrassFadeDistancePercent(FadeBandPercent: Number) // Default: 50
|
|
869
947
|
SetGrassFarThinningPower(Power: Number) // Default: 3
|
|
948
|
+
SetTreeLodSwitchDistanceM(DistanceMeters: Number) // Leaf cards -> whole-tree impostor switch distance, desktop meters (mobile auto-scales down). Default: 0 = auto (140 / 90)
|
|
949
|
+
SetTreeMeshDistanceM(DistanceMeters: Number) // Real branch-mesh max draw distance, desktop meters; impostors only beyond. Default: 0 = auto (400 / 260)
|
|
950
|
+
SetTreeCullDistanceM(DistanceMeters: Number) // Trees vanish entirely past this, desktop meters (the outermost bound - it caps the mesh + leaf-switch ranges under it). Default: 0 = auto (800 / 300)
|
|
951
|
+
SetTreeFadeDistancePercent(FadeBandPercent: Number) // Card->impostor crossfade band as % of the switch distance, clamped 5..80. Default: 36
|
|
952
|
+
SetTreeFarLeafDensityPercent(DensityPercent: Number) // Leaf-card density kept across the fade band, clamped 10..300; <100 thins earlier (cheaper), >100 denser. Default: 100
|
|
870
953
|
SetAssetSubmeshHue(ModelAsset: Object, SubmeshIndex: Number, HueDegrees: Number)
|
|
871
954
|
SetAssetSubmeshSaturation(ModelAsset: Object, SubmeshIndex: Number, Saturation: Number) // Chroma multiplier: 1 = neutral, 0 = grayscale
|
|
872
955
|
SetAssetSubmeshBrightness(ModelAsset: Object, SubmeshIndex: Number, Brightness: Number) // Gain: 1 = neutral
|
|
873
956
|
SetAssetSubmeshContrast(ModelAsset: Object, SubmeshIndex: Number, Contrast: Number) // Multiplier around mid-gray: 1 = neutral, 0 = flat gray. Use only when the user asks for it specifically; Tint/Brightness/Saturation cover normal fitting
|
|
874
957
|
SetAssetSubmeshTint(ModelAsset: Object, SubmeshIndex: Number, TintRGB: Vector)
|
|
875
|
-
SetAssetSubmeshEmissionIntensity(ModelAsset: Object, SubmeshIndex: Number, Intensity: Number)
|
|
958
|
+
SetAssetSubmeshEmissionIntensity(ModelAsset: Object, SubmeshIndex: Number, Intensity: Number) // Linear glow multiplier for this submesh; composes with object emission
|
|
876
959
|
SetAssetSubmeshMinRoughness(ModelAsset: Object, SubmeshIndex: Number, MinRoughness: Number)
|
|
877
960
|
SetAssetSubmeshRoughnessSmoothStepRemap(ModelAsset: Object, SubmeshIndex: Number, Enabled: Bool)
|
|
878
961
|
SetAssetSubmeshNormalStrength(ModelAsset: Object, SubmeshIndex: Number, Strength: Number)
|
|
@@ -1005,8 +1088,8 @@ Object.GetRotation() -> Vector
|
|
|
1005
1088
|
Object.GetScale() -> Vector
|
|
1006
1089
|
Object.GetSize() -> Vector
|
|
1007
1090
|
Object.GetSizeY() -> Number
|
|
1008
|
-
Object.GetSizeX() -> Number
|
|
1009
|
-
Object.GetHeight() -> Number
|
|
1091
|
+
Object.GetSizeX() -> Number // Footprint X size in cm (Y: GetSizeY). Vertical size is GetHeight - there is no GetSizeZ
|
|
1092
|
+
Object.GetHeight() -> Number // Vertical (Z) size in cm
|
|
1010
1093
|
Object.GetHalfHeight() -> Number
|
|
1011
1094
|
Object.GetHeightInTiles() -> Number
|
|
1012
1095
|
Object.GetHalfHeightInTiles() -> Number
|
|
@@ -1024,7 +1107,7 @@ Object.MoveTowardContinuousHorizontal(Position: Position, Speed: Number)
|
|
|
1024
1107
|
Object.MoveTowardContinuousHorizontalFacing(Position: Position, Speed: Number) // Facing means object's forward (local +X) is aligned to world-space Direction
|
|
1025
1108
|
Object.MoveInDirectionContinuously(Direction: Vector, Speed: Number)
|
|
1026
1109
|
Object.MoveInDirectionContinuouslyFacing(Direction: Vector, Speed: Number) // Facing means object's forward (local +X) is aligned to world-space Direction. On players with SetOrientingWithCameraYaw(true) the facing part is ignored (camera yaw owns the body).
|
|
1027
|
-
Object.MoveWithVelocityContinuously(Velocity: Vector) // On gravity-affected characters (the PlayerObject default) the Z component is silently DISCARDED - gravity/jump owns vertical velocity; only X/Y apply. For flying/hovering turn gravity off first (
|
|
1110
|
+
Object.MoveWithVelocityContinuously(Velocity: Vector) // On gravity-affected characters (the PlayerObject default) the Z component is silently DISCARDED - gravity/jump owns vertical velocity; only X/Y apply. For flying/hovering turn gravity off first (EnableCharacterGravity(false); players: SetPlayerModeHover); for jumps use Jump(). Gravity-off and bodyless objects honor full 3D
|
|
1028
1111
|
Object.MoveWithVelocityContinuouslyFacing(Velocity: Vector)
|
|
1029
1112
|
Object.RotateLeftInstantly(Degree: Number)
|
|
1030
1113
|
Object.RotateRightInstantly(Degree: Number)
|
|
@@ -1047,12 +1130,12 @@ Object.GetPlayerInputDirectionCamera(ForwardKey: Number, BackwardKey: Number, Le
|
|
|
1047
1130
|
Object.GetMoveVectorWorld() -> Vector
|
|
1048
1131
|
Object.GetMoveVectorCamera() -> Vector
|
|
1049
1132
|
IsKeyPressed(Key: Number) -> Bool
|
|
1050
|
-
Object.GetMousePositionDelayed() -> Vector // Player only. Sim cursor, 1600x900 base space; delayed a few frames behind the OS cursor. Clicks/hover/aim/drag/tooltips have dedicated calls (Click events, IsMouseOnUi, GetMouseTarget*, SetUiDraggable, SetUiFollowsCursor)
|
|
1133
|
+
Object.GetMousePositionDelayed() -> Vector // Player only. Sim cursor, 1600x900 base space (same space as UI rects; can run negative/past-base in the letterbox bands of non-16:9 windows); delayed a few frames behind the OS cursor, so a read cached across frames is NOT a click/drag anchor - under load it lags the press; anchor on Event.MouseDownPosition/Event.MousePosition. Clicks/hover/aim/drag/tooltips have dedicated calls (Click events, IsMouseOnUi, GetMouseTarget*, SetUiDraggable, SetUiFollowsCursor)
|
|
1051
1134
|
Object.GetMouseDirection() -> Vector
|
|
1052
|
-
Object.IsMouseOnUi() -> Bool //
|
|
1135
|
+
Object.IsMouseOnUi() -> Bool // True if the cursor's click target (topmost interactable or block_input element) is this element or inside it; screenCanvas receiver: over ANY of that canvas's clickable/blocking elements (empty canvas space stays false) - the usual whole-HUD gate. Gate raw click/action handlers with it. Plain labels/images never match (give them block_input=true or a class). Shared canvas: any player. Owned canvas: owner only.
|
|
1053
1136
|
Object.GetMouseTargetObject() -> Object
|
|
1054
1137
|
Object.GetMouseTargetPosition() -> Position
|
|
1055
|
-
Object.GetMouseTargetPositionOnPlaneZ(PlaneZ: Number) -> Position
|
|
1138
|
+
Object.GetMouseTargetPositionOnPlaneZ(PlaneZ: Number) -> Position // Player only. Cursor-ray hit on plane z=PlaneZ; Invalid position when the ray misses (cursor at/above horizon) - gate with .IsValid()
|
|
1056
1139
|
Object.GetMouseTargetNormal() -> Vector
|
|
1057
1140
|
Object.GetMouseTargetObjectWithTag(Tag: Number) -> Object
|
|
1058
1141
|
Object.GetMouseTargetPositionWithTag(Tag: Number) -> Position
|
|
@@ -1062,7 +1145,7 @@ Object.GetMouseTargetPlayer() -> Object
|
|
|
1062
1145
|
Object.GetMouseTargetPlayerWithTag(Tag: Number) -> Object
|
|
1063
1146
|
Object.GetCrosshairDirection() -> Vector
|
|
1064
1147
|
Object.GetCrosshairTargetObject() -> Object
|
|
1065
|
-
Object.GetCrosshairTargetPosition() -> Position // On miss, returns a point
|
|
1148
|
+
Object.GetCrosshairTargetPosition() -> Position // On miss, returns a point 1000 units along the crosshair ray. For gunfire: bullet Dir = (this - GetWeaponAnchorWorldPos("muzzle")).GetNormal().
|
|
1066
1149
|
Object.GetCrosshairTargetNormal() -> Vector
|
|
1067
1150
|
Object.GetCrosshairTargetObjectWithTag(Tag: Number) -> Object
|
|
1068
1151
|
Object.GetCrosshairTargetPositionWithTag(Tag: Number) -> Position
|
|
@@ -1144,7 +1227,7 @@ Object.AddTag(Tag: Number)
|
|
|
1144
1227
|
Object.HasTag(Tag: Number) -> Bool
|
|
1145
1228
|
Object.ClearTag()
|
|
1146
1229
|
Object.GetColor() -> Vector
|
|
1147
|
-
Object.IsEditorMode() -> Bool
|
|
1230
|
+
Object.IsEditorMode() -> Bool // Per-player state; player receiver (runtime error on non-players). From non-player classes call it on a player, e.g. GetAllPlayers().Get(0).IsEditorMode()
|
|
1148
1231
|
Object.IsMouseLookOn() -> Bool
|
|
1149
1232
|
Object.IsMouseVisible() -> Bool
|
|
1150
1233
|
Object.IsCrosshairVisible() -> Bool
|
|
@@ -1233,14 +1316,14 @@ TileGridFindPath8(Grid: List, StartX: Number, StartY: Number, GoalX: Number, Goa
|
|
|
1233
1316
|
TileGridPathTile(Grid: List, TileId: Number, OriginTile: Vector) -> Vector
|
|
1234
1317
|
TileGridPathWorld(Grid: List, TileId: Number, OriginTile: Vector) -> Position
|
|
1235
1318
|
Object.GetTile() -> Position
|
|
1236
|
-
Object.GetMouseTargetTileOnZeroZ() ->
|
|
1319
|
+
Object.GetMouseTargetTileOnZeroZ() -> Position // Player only. Tile index (hit/TileSizeXY floored) of the cursor ray on z=0; Invalid on miss - gate with .IsValid()
|
|
1237
1320
|
Object.WorldToScreenPosition(Position: Position) -> Vector // Base UI viewport coords (1600x900). Returns Vector(-1,-1,0) if behind/unprojectable.
|
|
1238
1321
|
Object.LocalOffsetToWorld(OffsetLocal: Position) -> Position // Origin: GetPosition()
|
|
1239
1322
|
Object.LocalDirectionToWorld(DirLocal: Vector) -> Vector
|
|
1240
|
-
Object.EquipWeapon(WeaponModel: Object) // attach a weapon model to this object's hand (render) + enable weapon anchors (sim)
|
|
1323
|
+
Object.EquipWeapon(WeaponModel: Object) // attach a weapon model to this object's hand (render) + enable weapon anchors (sim). Use for ALL player-held items - never a per-frame position-follow loop: scripts read sim aim, which trails the instantly-rendered camera, so the item swings ~100ms late on every turn. Aim/strafe games: add SetOrientingWithCameraYaw(true). Models with no grip data are held at a bbox guess (mid-body grips): give #mesh weapons socket("grip"); fix library/store models with SetModelAnchor
|
|
1241
1324
|
Object.UnequipWeapon() // remove the held weapon
|
|
1242
1325
|
Object.GetHeldWeapon() -> Object // held weapon model, or InvalidId when unarmed
|
|
1243
|
-
Object.GetWeaponAnchorWorldPos(AnchorName: Text) -> Position // AnchorName can be any model anchor; returns held-weapon anchor position in world space
|
|
1326
|
+
Object.GetWeaponAnchorWorldPos(AnchorName: Text) -> Position // AnchorName can be any model anchor; returns held-weapon anchor position in world space. Market-model anchors register only once the model has downloaded (OnSpawned can be too early); SetModelAnchor anchors are available immediately
|
|
1244
1327
|
Object.LookAtTileHorizontal(TileX: Number, TileY: Number)
|
|
1245
1328
|
Object.LookAtTileHorizontal(TileX: Number, TileY: Number, TileZ: Number)
|
|
1246
1329
|
Object.LookAtTileHorizontal(TileXYZ: Vector)
|
|
@@ -1258,10 +1341,11 @@ SpawnAtTile(SavedObject: Object, TileXYZ: Vector) -> Object
|
|
|
1258
1341
|
Object.TeleportToTile(TileX: Number, TileY: Number)
|
|
1259
1342
|
Object.TeleportToTile(TileX: Number, TileY: Number, TileZ: Number)
|
|
1260
1343
|
Object.TeleportToTile(TileXYZ: Vector)
|
|
1261
|
-
Object.SetRotation(Rotation: Vector) // Rotates about GetPosition() (bottom-center pivot for mesh objects). To spin about the middle: SetRotation(R) then SetCenterPosition(C) with C the fixed world center
|
|
1262
|
-
Object.SetRotation(X: Number, Y: Number, Z: Number) // Rotates about GetPosition() (bottom-center pivot for mesh objects). To spin about the middle: SetRotation(R) then SetCenterPosition(C) with C the fixed world center
|
|
1344
|
+
Object.SetRotation(Rotation: Vector) // Rotates about GetPosition() (bottom-center pivot for mesh objects). To spin about the middle: SetRotation(R) then SetCenterPosition(C) with C the fixed world center; any other hinge point: SetRotationWithPivot
|
|
1345
|
+
Object.SetRotation(X: Number, Y: Number, Z: Number) // Rotates about GetPosition() (bottom-center pivot for mesh objects). To spin about the middle: SetRotation(R) then SetCenterPosition(C) with C the fixed world center; any other hinge point: SetRotationWithPivot
|
|
1263
1346
|
Object.SetGroupRotationWithPivot(Pivot: AnyBasicType, Rotation: Vector)
|
|
1264
1347
|
Object.LookAtGroupWithPivot(Pivot: AnyBasicType, Target: AnyBasicType)
|
|
1348
|
+
Object.SetRotationWithPivot(Pivot: Position, Rotation: Vector) // Part swings around its middle instead of its mount (wing root, tail base, door edge) -> rigid-rotate about world-space Pivot: sets Rotation and moves GetPosition accordingly. Rejects gravity/force receivers. Group receivers: SetGroupRotationWithPivot
|
|
1265
1349
|
Object.SetScale(Scale: Vector) // raw multiplier of the model's authored size; prefer SetSize (world-unit footprint) for world objects AND effects (effects: size scales vs their boundingBoxSize)
|
|
1266
1350
|
Object.SetScale(X: Number, Y: Number, Z: Number) // raw multiplier of the model's authored size; prefer SetSize (world-unit footprint) for world objects AND effects (effects: size scales vs their boundingBoxSize)
|
|
1267
1351
|
Object.SetSize(Size: Vector) // Resizes around the object's CENTER: a grounded object sinks by half the growth (re-ground after, or use ScaleToHeight which stays base-anchored). Per-axis values stretch imported models - prefer uniform scaling for marketplace meshes
|
|
@@ -1271,11 +1355,12 @@ Object.SetSizeTilesUniform(Size: Number)
|
|
|
1271
1355
|
Object.SetSizeTiles(Size: Vector)
|
|
1272
1356
|
Object.SetSizeTiles(X: Number, Y: Number, Z: Number)
|
|
1273
1357
|
Position.IsCloseXY(Other: Position) -> Bool
|
|
1358
|
+
Position.IsValid() -> Bool // False for Invalid positions (the GetMouseTarget* miss sentinel); position-writing calls (SetPosition/TeleportTo/Spawn*) error on Invalid
|
|
1274
1359
|
Object.ScaleToHeight(Height: Number) // Uniform scale anchored at the base/bottom (feet stay planted) - the safe resize for characters and imported models. SetSize* grows around the CENTER: a grounded object sinks by half the growth
|
|
1275
1360
|
Object.SnapToGround() // Snap bottom-center downward to first collider; ignores prior Z offsets
|
|
1276
1361
|
Object.SnapToGroundWithTag(Tag: Number)
|
|
1277
1362
|
Object.SnapToGroundIgnoreTag(Tag: Number)
|
|
1278
|
-
Object.SetColor(RGB: Vector)
|
|
1363
|
+
Object.SetColor(RGB: Vector) // Tint that MULTIPLIES the material's existing colors (white = unchanged, black = black); it does not replace the albedo
|
|
1279
1364
|
Object.SetColor(R: Number, G: Number, B: Number)
|
|
1280
1365
|
Object.SetOpacity(Opacity01: Number)
|
|
1281
1366
|
Object.SetDebugMetallic(Metallic01: Number)
|
|
@@ -1288,19 +1373,21 @@ Object.SetWaterDeepColor(RGB: Vector) // Deep-water tint.
|
|
|
1288
1373
|
Object.SetWaterDeepColor(R: Number, G: Number, B: Number)
|
|
1289
1374
|
Object.SetWaterColorMixRange(ShallowCm: Number, DeepCm: Number) // Depth range for shallow-to-deep tint.
|
|
1290
1375
|
Object.SetWaterTransparency(Alpha: Number) // Water alpha/refraction blend.
|
|
1291
|
-
Object.SetEmissionColor(RGB: Vector)
|
|
1292
|
-
Object.SetEmissionColor(R: Number, G: Number, B: Number)
|
|
1293
|
-
Object.SetEmissionIntensity(Intensity: Number)
|
|
1376
|
+
Object.SetEmissionColor(RGB: Vector) // Glow tint; unset/near-black = glows in the object's own color
|
|
1377
|
+
Object.SetEmissionColor(R: Number, G: Number, B: Number) // Glow tint; unset/near-black = glows in the object's own color
|
|
1378
|
+
Object.SetEmissionIntensity(Intensity: Number) // Linear glow: 0 off, 1 soft self-lit, 4 lamp, 16+ neon bloom
|
|
1294
1379
|
Object.SetLightIntensity(Intensity: Number)
|
|
1295
|
-
Object.SetLightFadeWidth(
|
|
1380
|
+
Object.SetLightFadeWidth(FadeWidthCm: Number) // diffuseOnlyLight only (edge fade band; default 30, clamped to the light radius)
|
|
1296
1381
|
Object.SetLightCastShadow(Enabled: Bool)
|
|
1297
1382
|
Object.SetLightDiffuseScale(Scale: Number)
|
|
1298
1383
|
Object.SetLightSpecularScale(Scale: Number)
|
|
1299
|
-
Object.GetLightFadeWidth() -> Number
|
|
1384
|
+
Object.GetLightFadeWidth() -> Number // diffuseOnlyLight only
|
|
1300
1385
|
Object.GetLightDiffuseScale() -> Number
|
|
1301
1386
|
Object.GetLightSpecularScale() -> Number
|
|
1387
|
+
Object.SetLightRadius(RadiusCm: Number)
|
|
1388
|
+
Object.GetLightRadius() -> Number
|
|
1302
1389
|
Object.SetTeam(TeamId: Number)
|
|
1303
|
-
Object.GetTeam() -> Number // Unset team = 32767 (no SetTeam yet; players spawn teamless). Compare against team ids you assigned; never assume 0/1.
|
|
1390
|
+
Object.GetTeam() -> Number // Unset team = 32767 (no SetTeam or scene `team =` yet; players spawn teamless). Compare against team ids you assigned; never assume 0/1.
|
|
1304
1391
|
Object.SetPlayerOwnerIndex(PlayerIndex: Number)
|
|
1305
1392
|
Object.GetPlayerOwnerIndex() -> Number
|
|
1306
1393
|
Object.GetPlayerName() -> Text
|
|
@@ -1321,8 +1408,10 @@ Object.SpawnFloatingText(Text: Text)
|
|
|
1321
1408
|
Object.SpawnFloatingText(Text: Text, Color: Vector)
|
|
1322
1409
|
Object.SetHoverHPBar(HP: Number, MaxHP: Number)
|
|
1323
1410
|
Object.SetHoverSPBar(SP: Number, MaxSP: Number)
|
|
1324
|
-
Object.SetHoverBar(BarIndex: Number, Value: Number, MaxValue: Number, Color: Vector, BackgroundColor: Vector, OffsetX: Number, OffsetY: Number, Width: Number, Height: Number)
|
|
1325
|
-
Object.ClearHoverBars()
|
|
1411
|
+
Object.SetHoverBar(BarIndex: Number, Value: Number, MaxValue: Number, Color: Vector, BackgroundColor: Vector, OffsetX: Number, OffsetY: Number, Width: Number, Height: Number) // Floating stat bar above the caller at BarIndex 0..=29 (30/31 are the built-in SP/HP bars — use SetHoverSPBar/SetHoverHPBar). MaxValue <= 0 hides the bar. Overlapping bars on one object auto-separate toward their intended side; offsets need not be pixel-exact
|
|
1412
|
+
Object.ClearHoverBars() // Remove all of the caller's hover bars; SetHoverText("") clears the label. Objects reused without Destroy (repositioned indicators, manual pools) keep their previous life's bars and label - clear on repurpose
|
|
1413
|
+
Object.SetHoverText(Text: Text) // Persistent label above the object (unit names, vendor tags); empty Text removes it
|
|
1414
|
+
SetPlayerNamesVisible(IsVisible: Bool) // Default: visible. Hide the engine's username labels when drawing custom nameplates
|
|
1326
1415
|
DrawLine(Start: Position, End: Position, Color: Vector, Thickness: Number)
|
|
1327
1416
|
DrawSphere(Position: Position, Radius: Number, Color: Vector)
|
|
1328
1417
|
DrawSphere(Position: Position, Radius: Number)
|
|
@@ -1331,16 +1420,24 @@ DrawDot(Position: Position)
|
|
|
1331
1420
|
SpawnEffect(Effect: Object, Position: Position) -> Object
|
|
1332
1421
|
SpawnEffectWithAutoDestroy(Effect: Object, Position: Position) -> Object
|
|
1333
1422
|
SpawnEffectForDuration(Effect: Object, Position: Position, Duration: Number) -> Object
|
|
1423
|
+
SpawnDecal(Material: Object, Position: Position, RotationEuler: Vector, Size: Vector) -> Object // Projects onto geometry inside its oriented box along local -Z (down at rotation 0); Size z = projection depth cm. Bullet holes/stains/scorch marks. No engine pool: keep spawned decals in a List and Destroy the oldest past a few hundred
|
|
1424
|
+
SetScreenShader(Material: Object) // Fullscreen screen effect (film grain, color grade, distortion, damage flash): a #shader material applied after tonemap/AA; fragment reads ctx.color/ctx.depth. None clears. Animate via SetMaterialParameterNumber; see shader_dsl skill
|
|
1425
|
+
SetVolumetricLightIntensity(Percent: Number) // Sun shafts / god rays (raymarched vs the sun's shadows). Default: 0 (off); ~40 = subtle atmosphere, 100+ = dramatic alley/doorway shafts. Reads best with a low sun + occluding geometry
|
|
1426
|
+
SetVolumetricLightDensity(Percent: Number) // Haze thickness the shafts scatter in, percent of baseline. Default: 100; lower = crisper thinner shafts, higher = foggier
|
|
1427
|
+
Object.EnableLocalWeaponView(Offset: Position, RotationDegrees: Vector, Scale: Number, FovDegrees: Number) // EnableLocalWeaponView + own weapon FOV: FovDegrees 0 = follow camera; ~50-70 = classic FPS viewmodel that ignores ADS zoom and never clips walls
|
|
1428
|
+
SetSoundVolume(Sound: Object, Volume: Number) // Live per-asset volume (1 = as authored): retunes every instance already playing, loops included, and every later play; the per-play PlaySound volume multiplies on top. 0 mutes but keeps loops running - StopSound to end them
|
|
1429
|
+
FadeOutSound(Sound: Object, Seconds: Number) // Ramps this sound's playing instances to silence over Seconds, then stops them; queued plays drop immediately, plays started after the call are unaffected. Music/ambience handoffs - StopSound cuts instantly
|
|
1334
1430
|
PlaySound(Sound: Object, Volume: Number) // Global (every client, full volume): UI/music. Positional per-player attenuation: PlaySoundAtPosition; stop an asset's instances: StopSound
|
|
1335
1431
|
PlaySound(Sound: Object, Volume: Number, Pitch: Number)
|
|
1336
1432
|
PlaySoundLooped(Sound: Object, Volume: Number) // Loops until StopSound(sound)/StopAllSounds; for BGM/engine hum (PlaySound stays one-shot)
|
|
1433
|
+
PlaySoundForPlayer(Player: Object, Sound: Object, Volume: Number) // Plays only on that player's client (global mix, no position): personal announcements/feedback the other players must not hear. Runtime error if Player is not a live player
|
|
1337
1434
|
SetSoundAssetPriority(Sound: Object, Priority: Number)
|
|
1338
1435
|
SetSoundAssetCooldown(Sound: Object, CooldownSeconds: Number)
|
|
1339
1436
|
SetMaxSimultaneousSoundEffect(Max: Number)
|
|
1340
|
-
GetRayHitPosition(Position: Position, Direction: Vector, Tag: Number) -> Position // Tag filters
|
|
1341
|
-
GetRayHitObject(Position: Position, Direction: Vector, Tag: Number) -> Object
|
|
1342
|
-
GetRayHitStaticPosition(Position: Position, Direction: Vector, Tag: Number) -> Position // Tag filters
|
|
1343
|
-
GetRayHitStaticObject(Position: Position, Direction: Vector, Tag: Number) -> Object
|
|
1437
|
+
GetRayHitPosition(Position: Position, Direction: Vector, Tag: Number) -> Position // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. No max-distance param (range-gate on GetRayHitDistance); on miss returns Position + Direction * 1000000.
|
|
1438
|
+
GetRayHitObject(Position: Position, Direction: Vector, Tag: Number) -> Object // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Returns null Object on miss (check .Exists()).
|
|
1439
|
+
GetRayHitStaticPosition(Position: Position, Direction: Vector, Tag: Number) -> Position // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. No max-distance param (range-gate on GetRayHitDistance); on miss returns Position + Direction * 1000000.
|
|
1440
|
+
GetRayHitStaticObject(Position: Position, Direction: Vector, Tag: Number) -> Object // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Returns null Object on miss (check .Exists()).
|
|
1344
1441
|
Object.SetCameraThisFrame(Position: Position, YawDegrees: Number, PitchDegrees: Number)
|
|
1345
1442
|
Object.SetCameraLookAtThisFrame(Position: Position, LookAt: Position)
|
|
1346
1443
|
Object.SetChaseCameraThisFrame(TargetPosition: Position, ForwardDirection: Vector, BackDistance: Number, Height: Number, LookAtHeight: Number) // Places camera at TargetPosition - normalize(ForwardDirection.xy)*BackDistance + Z*Height, looking at TargetPosition + Z*LookAtHeight
|
|
@@ -1363,18 +1460,7 @@ PersistGetNumber(Player: Object, Key: Text) -> Number
|
|
|
1363
1460
|
PersistSetNumber(Player: Object, Key: Text, Value: Number)
|
|
1364
1461
|
PersistAddNumber(Player: Object, Key: Text, Delta: Number)
|
|
1365
1462
|
RefreshStaticBaseline() // Call once at the end of heavy static-scene init; non-moving objects then skip per-frame display CPU (a later manipulation opts that object out).
|
|
1366
|
-
RestartGame()
|
|
1367
|
-
AdvertiseLobbySession() // Advertise this relay as a joinable lobby session for the running project; not a gameplay replication/server-authority API.
|
|
1368
|
-
StopAdvertisingLobbySession() // Stop advertising this relay lobby session.
|
|
1369
|
-
ListLobbySessions() // Request discoverable lobby sessions of the running project (identity automatic, exact content version). Results update LobbySession* getters and fire OnLobbySessionListUpdated; state 2 with 0 sessions = nobody hosts this exact version.
|
|
1370
|
-
JoinLobbySession(RelayPubkey: Text) // Join a discovered relay lobby session by LobbySessionRelayPubkey; deterministic shared sim still applies after joining.
|
|
1371
|
-
LobbySessionCount() -> Number // Number of discovered relay lobby sessions from the latest ListLobbySessions result.
|
|
1372
|
-
LobbySessionRelayPubkey(Index: Number) -> Text // Relay pubkey for a discovered lobby session; pass to JoinLobbySession.
|
|
1373
|
-
LobbySessionConnectId(Index: Number) -> Text // Debug/connect id for a discovered relay lobby session; usually not needed for gameplay code.
|
|
1374
|
-
LobbySessionDisplayName(Index: Number) -> Text // Human display name for a discovered relay lobby session.
|
|
1375
|
-
LobbySessionRefreshState() -> Number // Latest list state: 0 idle, 1 pending, 2 ok, 3 error (read LobbySessionErrorText).
|
|
1376
|
-
LobbySessionStage(Index: Number) -> Text // #stage name a discovered session is currently on (empty for stageless projects); show or filter it in the session browser.
|
|
1377
|
-
LobbySessionErrorText() -> Text // Reason for state 3 (empty otherwise), e.g. relay unreachable or no loaded world.
|
|
1463
|
+
RestartGame() // Reloads the WHOLE project (fresh load flow; discards the user's unsaved manual editor edits). Hard New Game only - pair with PersistClear(). Round/match resets: reset positions/scores in code instead.
|
|
1378
1464
|
AnyBasicType.NearestPlayerNonSpectator() -> Object
|
|
1379
1465
|
SetSsrRoughnessFade(StartRoughness: Number, EndRoughness: Number) // Default: 0.68, 0.7
|
|
1380
1466
|
SetSsrRayFade(NearFadePower: Number, FarFadePower: Number) // Default: 0.08, 0.8
|
|
@@ -1383,16 +1469,15 @@ SetSkyboxContrast(Contrast: Number) // Contrast multiplier. Default: 1 = neutral
|
|
|
1383
1469
|
Hash2(X: Number, Y: Number, Seed: Number) -> Number // Deterministic [0,1): constant within each integer X/Y cell (see <procedural_generation>)
|
|
1384
1470
|
ValueNoise2(X: Number, Y: Number, Seed: Number) -> Number // Deterministic smooth noise [0,1), X/Y in cell units
|
|
1385
1471
|
Fbm2(X: Number, Y: Number, Octaves: Number, Seed: Number) -> Number // Deterministic fractal noise [0,1), Octaves 1..8
|
|
1386
|
-
SpawnTerrainFromHeights(Heights: List, SamplesX: Number, SamplesY: Number, CellSize: Number, Position: Position) -> Object // Heights: row-major List of Numbers (cm, relative to Position.Z), len = SamplesX*SamplesY (2..513 each)
|
|
1387
|
-
SpawnTerrainFromHeights(Name: Text, Heights: List, SamplesX: Number, SamplesY: Number, CellSize: Number, Position: Position) -> Object // Same + registers terrain asset under Name (error if name taken)
|
|
1472
|
+
SpawnTerrainFromHeights(Heights: List, SamplesX: Number, SamplesY: Number, CellSize: Number, Position: Position) -> Object // Heights: row-major List of Numbers (cm, relative to Position.Z), len = SamplesX*SamplesY (2..513 each). Terrain XY is CENTERED on Position
|
|
1473
|
+
SpawnTerrainFromHeights(Name: Text, Heights: List, SamplesX: Number, SamplesY: Number, CellSize: Number, Position: Position) -> Object // Same + registers terrain asset under Name (error if name taken or starting with 'terrain.' - reserved for uploaded assets)
|
|
1388
1474
|
Object.SetTerrainMaterial(MaterialAsset: Object) // Terrain objects only; Material.X (#shader or #texture auto-material, triplanar works), None clears
|
|
1389
1475
|
Object.SetTerrainLayers(Steep: Object, High: Object, Low: Object) // Terrain only; blends extra #texture materials over the base by slope/height: Steep on cliffs, High above HighStartZ, Low below LowStartZ. None skips a layer. Set base via SetTerrainMaterial
|
|
1390
1476
|
Object.SetTerrainLayerParams(SlopeStartDeg: Number, SlopeEndDeg: Number, HighStartZ: Number, HighEndZ: Number, LowStartZ: Number, LowEndZ: Number) // Terrain only; tunes SetTerrainLayers blending. Slope in degrees (start..end ramp), heights in world Z cm. 0,0 for a pair = auto (slope 32..50, High top ~25%, Low bottom ~15% of height range)
|
|
1391
|
-
CreateInstanceBatch(Model: Object, Origin: Position) -> Object // Display-only mass scatter batch (see <procedural_generation>); Model: Ori.model.* (basics like Ori.model.cone work) or Ori.alias.model.* - mesh models ONLY (effects/images/spawned Objects are rejected at runtime). Max 128 batches per world
|
|
1392
1477
|
Object.AddInstance(Offset: Vector, RotationEuler: Vector, Size: Vector, ColorRGB: Vector) // Batch only. Offset cm from batch origin (within +-30000), RotationEuler deg, Size cm, ColorRGB 0..1
|
|
1393
1478
|
Object.ClearInstances() // Batch only; removes all instances
|
|
1394
1479
|
Object.GetInstanceCount() -> Number // Batch only
|
|
1395
|
-
Object.SetBatchOrigin(Origin: Position) // Batch only; moves whole batch (offsets stay relative)
|
|
1480
|
+
Object.SetBatchOrigin(Origin: Position) // Batch only; moves whole batch (offsets stay relative). Counts as a batch edit (full re-upload) - not a per-frame motion lane
|
|
1396
1481
|
Object.SetBatchCastShadow(Enabled: Bool) // Batch only; default true
|
|
1397
1482
|
Object.SetBatchMaterial(Material: Object) // Batch only; Material.X (#shader/#texture auto-material) replaces all submesh materials of every instance, None reverts to the model's own
|
|
1398
1483
|
Object.ShowSelectionRing(Player: Object, Radius: Number, Color: Vector) // RTS selected-unit ground circle on caller; visible only to Player; persists until Hide.
|
|
@@ -1401,7 +1486,7 @@ Object.ShowOutlineHighlight(Player: Object, ThicknessPixels: Number, Color: Vect
|
|
|
1401
1486
|
Object.HideOutlineHighlight(Player: Object) // Removes this player's outline on caller (no-op if absent)
|
|
1402
1487
|
Object.EnableLocalSelectionBox(BorderColor: Vector, FillColor: Vector, FillOpacity: Number, BorderWidthPixels: Number, MinDragPixels: Number) // PlayerObject only. Enables a visual-only local drag box for this player; gameplay selection should use Event.MouseDownPosition/Event.MousePosition
|
|
1403
1488
|
Object.DisableLocalSelectionBox() // PlayerObject only. Disables the visual-only local drag box
|
|
1404
|
-
Object.EnableLocalWeaponView(Offset: Position, RotationDegrees: Vector, Scale: Number) // PlayerObject only. Renders this player's equipped weapon as a local first-person camera-attached visual for the owning client (active only in first-person camera). Offset is camera-local cm (+X right, +Y up, +Z forward); RotationDegrees (0,0,0) = barrel forward; e.g. (Position(15, -25, 32), Vector(2, 4, 0), 1.0) for an FPS-style close lower-right framing (a close-in gun keeps the auto-arms shoulder area off-screen). Visual-only; gameplay still uses EquipWeapon/GetHeldWeapon.
|
|
1489
|
+
Object.EnableLocalWeaponView(Offset: Position, RotationDegrees: Vector, Scale: Number) // PlayerObject only. Renders this player's equipped weapon as a local first-person camera-attached visual for the owning client (active only in first-person camera). Offset is camera-local cm (+X right, +Y up, +Z forward); RotationDegrees is a first-person camera tilt, (0,0,0) = barrel forward (third person seats the weapon on the hand frame + grip anchor, not on this value); e.g. (Position(15, -25, 32), Vector(2, 4, 0), 1.0) for an FPS-style close lower-right framing (a close-in gun keeps the auto-arms shoulder area off-screen). Visual-only; gameplay still uses EquipWeapon/GetHeldWeapon.
|
|
1405
1490
|
Object.DisableLocalWeaponView() // PlayerObject only. Disables the local first-person weapon visual
|
|
1406
1491
|
Object.GetWeaponAnchorWorldDir(AnchorName: Text) -> Vector // Anchor local +X (forward) transformed to world space
|
|
1407
1492
|
Object.GetWeaponAnchorWorldUp(AnchorName: Text) -> Vector // Anchor local +Z transformed to world space
|
|
@@ -1411,7 +1496,7 @@ TileGridCreate(TilesX: Number, TilesY: Number, DefaultWalkable: Bool, EdgeBlocki
|
|
|
1411
1496
|
TileGridSetEdgeBlocked(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number, Blocked: Bool)
|
|
1412
1497
|
TileGridIsEdgeBlocked(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number) -> Bool
|
|
1413
1498
|
Object.GetCenterPosition() -> Position
|
|
1414
|
-
Object.SetCenterPosition(Position: Position) // Center-anchored twin of SetPosition (same continuous moved semantics)
|
|
1499
|
+
Object.SetCenterPosition(Position: Position) // Center-anchored twin of SetPosition (same continuous moved semantics). The center resolves against the object's CURRENT rotation, so rotate first, then place (reversed order drifts rotated parts by half a box). On UI/canvas elements both calls place the element's TOP-LEFT (pivot 0,0); call SetUiPivot(1,1) once to center — GetCenterPosition mirrors the same convention
|
|
1415
1500
|
Object.SetUiFollowsCursor(IsOn: Bool, OffsetX: Number, OffsetY: Number) // UI element on an owned screen canvas. Engine pins element+children at the owner's cursor + offset every frame, clamped on-screen, drawn latency-free. For tooltips/drag images, not clickable elements
|
|
1416
1501
|
Object.SetRenderVisibleOnlyToPlayerIndex(PlayerIndex: Number) // World object render/pick visibility only for this player index; also sets owner metadata
|
|
1417
1502
|
Object.SetRenderVisibleToAllPlayers() // Clears private render/pick visibility; does not clear owner metadata
|
|
@@ -1421,14 +1506,17 @@ Object.StopUpperBodyAnimation() // Base clip retakes the upper bones instantly;
|
|
|
1421
1506
|
Object.SetCursorImage(ImageAsset: Object)
|
|
1422
1507
|
Object.SetCursorImage(ImageAsset: Object, HotspotX: Number, HotspotY: Number, Opacity: Number)
|
|
1423
1508
|
Object.SetWeaponAimCalibration(IsOn: Bool) // Default true: while OrientingWithCameraYaw, the upper body twists so the muzzle (in the weapon-hold pose) aligns with the camera/crosshair ray (aim offset); locomotion bob passes through. Turn off for reloads/cinematic poses where the gun shouldn't track the crosshair.
|
|
1509
|
+
Object.SetCursorAim(IsOn: Bool) // Cursor-aim games (top-down/twin-stick): the player's rendered body yaw tracks their live cursor between sim frames, hiding input latency on their own screen; the sim rotation your per-frame LookAt sets stays authoritative. Muzzle/body trails a fast-moving mouse -> turn this on.
|
|
1510
|
+
Object.SetCameraFixed(Position: Position, LookAt: Position) // Parks the camera at Position looking at LookAt until a camera-mode switch releases it: persistent across frames AND save/reload, camera collision off while fixed. Security cams, lobby shots, cinematics - no per-frame loop needed.
|
|
1424
1511
|
SpawnAIPlayer(Name: Text) -> Object // Spawns a bot: a real PlayerObject ('#class player' code, physics, animations) driven by script instead of a user
|
|
1425
1512
|
Object.IsAIPlayer() -> Bool // True only for players spawned via SpawnAIPlayer()
|
|
1513
|
+
Object.IsConnected() -> Bool
|
|
1426
1514
|
Object.SetAIMoveVector(Direction: Vector) // Bot move input, like a held stick (persists until changed; xy, auto-clamped to length 1). Takes effect next frame
|
|
1427
1515
|
Object.PressAIAction(Action: Number) // Bot action tap (Action.Jump etc). Down fires next frame, auto-releases the frame after
|
|
1428
1516
|
Object.DespawnAIPlayer() // Removes a bot (errors on real players). Plain Destroy() also works on bots
|
|
1429
1517
|
Object.SetTerrainGrass(Model: Object, Material: Object) // Terrain only; Model: Ori.model.* or Ori.model.mesh.* (#mesh blade), None = built-in grass (tuft cards; see SetTerrainGrassStyle); Material: Material.X (#texture auto-material) or None = built-in atlas (usually enough; colors come from the grass knobs). On tuft cards the material's texture replaces the built-in card atlas (2x2 variant cells, uv+clamp+cutout; greyscale luminance = root->tip gradient, color still comes from the grass color knobs). Per-terrain (each terrain can have its own grass style)
|
|
1430
1518
|
Object.SetTerrainGrassWeightmap(Material: Object) // Terrain only; Material: #texture auto-material; R channel of its color = grass density 0..1 across the terrain; None clears (full density). Overrides #terrain_splat out.grass; prefer out.grass when density should follow the terrain (height/slope), since a #texture cannot see it
|
|
1431
|
-
Object.SetWeaponConfig(WeaponModel: Object, IdleAnim: Object, ReloadAnim: Object, FireAnim: Object, ForegripAnchor: Text, GripAnchor: Text, MuzzleAnchor: Text, FireInterval: Number, ReloadDuration: Number) // Register per-weapon-model
|
|
1519
|
+
Object.SetWeaponConfig(WeaponModel: Object, IdleAnim: Object, ReloadAnim: Object, FireAnim: Object, ForegripAnchor: Text, GripAnchor: Text, MuzzleAnchor: Text, FireInterval: Number, ReloadDuration: Number) // Register per-weapon-model held-weapon config. IdleAnim auto-plays as upper-body loop on EquipWeapon (if no upper animation is active); anims accept InvalidId = off. Anchor names may be "" = built-in defaults (foregrip/grip). GripAnchor seats third-person AND the viewmodel; no foregrip anchor on the model = support hand dropped = one-handed hold. FireInterval/ReloadDuration are seconds, readable via GetWeaponFireInterval/GetWeaponReloadDuration for script fire loops.
|
|
1432
1520
|
Object.GetWeaponFireInterval(WeaponModel: Object) -> Number // Seconds between shots from the weapon model's SetWeaponConfig (0 when unset)
|
|
1433
1521
|
Object.GetWeaponReloadDuration(WeaponModel: Object) -> Number // Reload seconds from the weapon model's SetWeaponConfig (0 when unset)
|
|
1434
1522
|
Object.SetEmitting(IsOn: Bool) // EffectObject only. false: stop spawning new particles (existing ones finish while still following the emitter); true: resume. Emitter stays alive.
|
|
@@ -1437,7 +1525,7 @@ Object.DestroyImmediate() // EffectObject only. Destroys the emitter and hard-cl
|
|
|
1437
1525
|
Object.ShowSelectionRing(Player: Object, Radius: Number, Color: Vector, ZOffset: Number) // As 3-arg; ZOffset cm shifts ring vertically from feet (negative grounds a flying unit's ring)
|
|
1438
1526
|
SetSkyboxCloudCoverage(Coverage: Number) // Procedural sky only (no SetSkybox asset). Default: 0.1; 0 = clear sky, 1 = overcast
|
|
1439
1527
|
SetSkyboxHaze(Haze: Number) // Procedural sky only. Default: 0.5; 0.3 = crisp alpine air, 2-3 = hazy horizon
|
|
1440
|
-
SetSkyboxStarIntensity(Intensity: Number) // Night star overlay brightness. Default: 1; 0 = no stars
|
|
1528
|
+
SetSkyboxStarIntensity(Intensity: Number) // Night star overlay brightness; visible only with the sun near/below the horizon or SetSkyboxIsNight(true). Default: 1; 0 = no stars
|
|
1441
1529
|
SetSkyboxCloudDetail(Detail: Number) // Procedural sky only. Cloud fractal detail. Default: 1; 0 = soft blobs, 2 = max wispy detail
|
|
1442
1530
|
SetSkyboxCloudScale(Scale: Number) // Procedural sky only. Cloud pattern size. Default: 1; 0.5 = big clouds, 2 = many small clouds
|
|
1443
1531
|
SetSkyboxGroundColor(Color: Vector) // Procedural sky only. Ground-bounce light color lighting surfaces from below. Default: (0.34, 0.44, 0.32) dim green; brightness follows the sky
|
|
@@ -1452,7 +1540,7 @@ Object.SetTerrainSplat(SplatRules: Text) // Terrain only; SplatRules: #terrain_s
|
|
|
1452
1540
|
SetWaterPlanarReflection(Strength: Number, Distortion: Number) // Mirror reflection of the scene on the GLOBAL water surface (SetWaterEnabled) only; placed water meshes always use skybox reflections. Strength 0 disables, Distortion scales wave wobble. Defaults: 0.7, 0.3
|
|
1453
1541
|
Object.SetHoverHPBarMaterial(Material: Object) // Custom look for caller's hover HP bar; Material.X (#shader or #texture auto-material), None reverts to default. #shader gets ctx.fraction/ctx.background_color and draws the whole bar as one quad (textures don't squash). Persists across SetHoverHPBar updates; ClearHoverBars removes it
|
|
1454
1542
|
Object.SetHoverSPBarMaterial(Material: Object) // Same as SetHoverHPBarMaterial for the hover SP bar
|
|
1455
|
-
Object.SetHoverBarMaterial(BarIndex: Number, Material: Object) // Custom look for caller's SetHoverBar bar at BarIndex (0
|
|
1543
|
+
Object.SetHoverBarMaterial(BarIndex: Number, Material: Object) // Custom look for caller's SetHoverBar bar at BarIndex (0..=29); Material.X or None reverts to default. See SetHoverHPBarMaterial for the #shader contract
|
|
1456
1544
|
SetDefaultHoverBarMaterial(Material: Object) // World default material for all hover bars (HP/SP/custom) without a per-object material; None reverts to built-in look
|
|
1457
1545
|
StopSound(Sound: Object) // Stops all currently playing and queued instances of this sound asset (e.g. cut off a long music one-shot)
|
|
1458
1546
|
StopAllSounds() // Stops every currently playing and queued sound
|
|
@@ -1534,12 +1622,14 @@ Object.GetTerrainHeightAt(X: Number, Y: Number) -> Number
|
|
|
1534
1622
|
Text.Word(Index: Number) -> Text
|
|
1535
1623
|
Text.WordCount() -> Number
|
|
1536
1624
|
Text.ToNumber() -> Number
|
|
1625
|
+
Text.Length() -> Number // Character count (chars, not bytes - Thai etc. count per character).
|
|
1626
|
+
Text.Substring(Start: Number, Count: Number) -> Text // Chars [Start, Start+Count), 0-based, clamped to the text; negative Start or Count <= 0 -> empty Text.
|
|
1537
1627
|
SetOcclusionCulling(Enabled: Bool) // Default: false (experimental Hi-Z occlusion culling)
|
|
1538
1628
|
TerrainHeightsErosionFilter(Heights: List, SamplesX: Number, SamplesY: Number, CellCm: Number, ScaleCells: Number, Octaves: Number, StrengthCm: Number, Seed: Number, Mask: List, RidgeMapOut: List) // Stateless erosion filter (Phacelle gullies, no droplet sim): slope-aligned V-gullies + dendritic ridges; altitude fade keeps peaks sharp and valley floors intact. ScaleCells=largest gully size, StrengthCm=max carve/raise, Octaves 1..8, Mask=0..1 weights ([]=all), RidgeMapOut (pass [] List, auto-sized) 0..1: 0=crease 1=ridge (splat/tree/drainage rules). The go-to eroded-mountain look. Heavy: cost ~ sx*sy*Octaves (513 grid + 5 octaves = noticeable load time; prefer <=257 grids or <=4 octaves when loads must stay snappy)
|
|
1539
1629
|
SetTerrainErosionDetailPercent(Percent: Number) // Global display-only knob: scales the GPU sub-cell erosion-gully detail on all terrain surfaces, and the displayed gully depth of #terrain recipe terrains (100 = default, clamped 0..300, 0 = smooth). Visual only — collision/heights unchanged; grass follows the displayed surface automatically
|
|
1540
1630
|
TerrainHeightsOriErosion(Heights: List, SamplesX: Number, SamplesY: Number, CellCm: Number, BaseScaleCells: Number, ErosionScaleCells: Number, Octaves: Number, StrengthCm: Number, SlopeStrength: Number, BranchStrength: Number, Seed: Number, Mask: List, ErosionMapOut: List) // OriErosion directional erosion (the #terrain recipe engine as a bulk op; docs/ori_erosion_spec.md): cos-wave gullies steered by the base slope with per-octave branch feedback. BaseScaleCells = base-noise feature size (for slope direction recovery), ErosionScaleCells = first-octave gully size, SlopeStrength ~3, BranchStrength ~0.5, StrengthCm = carve depth. Mask scales accumulation ([] = full); note masked zones sink ~0.5*StrengthCm (water datum). ErosionMapOut (pass [] List, auto-sized) = hx*0.5+0.5 for splat user-plane coloring
|
|
1541
1631
|
SetTerrainDisplayExtension(RadiusM: Number) // Display-only: renders procedural terrain BEYOND the sim grid out to RadiusM (0 = off), no collision. The generator params come from the world's #terrain block (required; errors without one) — build the sim grid with TerrainHeightsFromTerrain from the same block and the extension continues it seamlessly. Pair with SetCameraFarDistanceM to actually see it
|
|
1542
|
-
SetCameraFarDistanceM(Meters: Number) // Display-only camera far plane in meters; 0 = engine default 1000, else clamped 1000..8000. Distant terrain needs SetFogEndDistance raised too;
|
|
1632
|
+
SetCameraFarDistanceM(Meters: Number) // Display-only camera far plane in meters; 0 = engine default 1000, else clamped 1000..8000. Distant terrain needs SetFogEndDistance raised too; light clustering keeps near-field tuning; static geometry keeps sun shadows to ~2km (cached far layer; desktop)
|
|
1543
1633
|
Object.SpawnDamageNumber(Amount: Number) // Damage number above caller: pop-arc motion, world-scaled, size grows with Amount; rounds to integer. Call from WasAttacked with Event.Damage. For custom look use the 5-arg SpawnFloatingText overload
|
|
1544
1634
|
Object.SpawnFloatingText(Text: Text, Color: Vector, SizeScale: Number, Motion: Number, Anchor: Number) // Styled overload: SizeScale multiplies text size; Motion 0=RiseFade 1=PopArc 2=Sticky 3=BounceLand; Anchor 0=constant screen size 1=scales with camera distance
|
|
1545
1635
|
SetDefaultFloatingTextFont(Font: Object) // World default font for SpawnFloatingText/SpawnDamageNumber (e.g. Ori.font.google.Lobster); None reverts to built-in
|
|
@@ -1551,15 +1641,15 @@ Object.SetTerrainGrassSpawnMaterial(Material: Object) // Terrain only; Material:
|
|
|
1551
1641
|
Object.SetTerrainSurfaces(SurfacesName: Text) // Terrain only; SurfacesName: #terrain_surfaces block name (up to 8 tiled PBR surfaces packed into texture arrays; lines `surface = <albedo>, <normal>, <tiling_m>[, <orm>[, <grass01>]]` - optional orm alpha carries height for crisp height-blended transitions; optional grass01 = grass density factor on that surface (default 1 for the first surface, 0 for the rest, so grass skips rock/snow); pair with SetTerrainSurfaceWeightsMaterial for per-texel weights). "" clears
|
|
1552
1642
|
Object.SetTerrainSurfaceWeightsMaterial(Material: Object) // Terrain only; Material: material with a #shader - a CUSTOM surface-weights COMPUTE shader writing 8 blend weights per terrain texel (recipe terrains; covers the display extension via the surface clipmap). None clears
|
|
1553
1643
|
GetWaterHeightAt(X: Number, Y: Number) -> Number // Deterministic ocean surface height (cm) at world XY this frame - the wave swell all clients agree on (visual-only fine detail excluded). Use for floating/buoyancy. Returns sea level when the ocean is off or flat
|
|
1554
|
-
GetWaterNormalAt(X: Number, Y: Number) -> Vector // Deterministic ocean surface unit normal (Z-up) at world XY this frame - pair with GetWaterHeightAt to tilt floating objects (e.g.
|
|
1555
|
-
Object.SetMakesWaterWake(Enabled: Bool) // Opt this object into churning the global ocean: wake ripples + foam while moving through the surface band, and a splash on crossing the surface. Visual only. Tag boats/swimmers/thrown objects; players splash automatically without it. Default: off
|
|
1644
|
+
GetWaterNormalAt(X: Number, Y: Number) -> Vector // Deterministic ocean surface unit normal (Z-up) at world XY this frame - pair with GetWaterHeightAt to tilt floating objects (e.g. AlignRotationWith(GetWaterNormalAt(P.X, P.Y)) style alignment). Returns (0,0,1) when the ocean is off or flat
|
|
1645
|
+
Object.SetMakesWaterWake(Enabled: Bool) // Opt this object into churning the global ocean: wake ripples + foam while moving through the surface band, and a splash on crossing the surface. Visual only. Tag boats/swimmers/thrown objects; players splash automatically without it. Wake width scales with the hull's horizontal size (floor ~0.6 m); rings spread outward like real water. Default: off
|
|
1556
1646
|
Object.GetCrosshairRayOrigin() -> Position // Camera eye point the crosshair ray starts from (= this player's camera position; pair with GetCrosshairDirection for the camera pose). Use this (not GetPosition().ShiftUp(N), which sits below the eye and lands low) as the origin for hand-rolled crosshair rays/markers.
|
|
1557
1647
|
SetWaterSplashEffect(Effect: Object) // Replace the built-in ocean surface-crossing splash particle effect world-wide (all wake/splash objects and players). Pass an Effect asset (e.g. Ori.alias.effect.big_splash). Visual only; the built-in droplet burst is used until this is called
|
|
1558
1648
|
Object.SetCharacterOverlapIgnoreTag(Tag: Number, IsOn: Bool) // This character's overlap resolution skips objects carrying Tag (walk through them; ground/other collision unaffected, Touched/regions still fire). Call with IsOn=false to restore. Per-tag, stacks
|
|
1559
1649
|
Object.SetDefaultPlayerControlsEnabled(IsOn: Bool) // Weave2 only, player receiver. true: attach the built-in default player controls (WASD camera-relative move + jump) alongside this class — the one-liner for '#class player' hooks that don't code their own movement. false: detach them (freeze for cutscenes; classless-world players can be frozen from any class). Idempotent
|
|
1560
1650
|
Object.AttachTo(Parent: Object) // Rigidly ride Parent from the CURRENT relative pose (re-baked from Parent's post-physics transform every tick; overrides this object's own moves). Child must be collision-free: display/effect/light/marker objects, or SetCanCollide(false) first. Chains allowed (arm -> gun); cycles error. Replaces per-frame SetPosition follow loops. Order-sensitive in OnSpawned (parent may not be placed yet) - prefer the 3-arg overload there
|
|
1561
1651
|
Object.AttachTo(Parent: Object, LocalPosition: Position, LocalRotation: Vector) // Explicit-mount overload: LocalPosition (absolute cm) and LocalRotation (euler degrees) in Parent's local frame; safe in OnSpawned. The frame runs CENTER-to-CENTER (not the bottom-center pivot GetPosition/#mesh use): seat a child by adding half ITS height to the offset. Offsets don't rescale when Parent's size changes - re-attach after growth
|
|
1562
|
-
Object.AttachToAnchor(Parent: Object, AnchorName: Text) // AttachTo at a named model anchor of Parent's mesh (e.g. "muzzle", "grip"); errors when the anchor doesn't exist (anchors resolve after asset import). The anchor's axis is its local +Z but object/effect forward is local +X - directional effects on an anchor need a LocalRotation (usually RotY +-90).
|
|
1652
|
+
Object.AttachToAnchor(Parent: Object, AnchorName: Text) // AttachTo at a named model anchor of Parent's mesh (e.g. "muzzle", "grip"); errors when the anchor doesn't exist (anchors resolve after asset import). The anchor's axis is its local +Z but object/effect forward is local +X - directional effects on an anchor need a LocalRotation (usually RotY +-90). The mount point follows the parent's scale at attach time (where the socket visually sits); captured offsets don't re-follow LATER parent rescales. A `bone:`-bound socket anchor makes the child visually ride that bone's animation (sim pose stays rest)
|
|
1563
1653
|
Object.Detach() // Remove this object's attachment; it keeps its current world transform
|
|
1564
1654
|
Object.GetAttachParent() -> Object // Attachment parent, or None-like non-existing Object when not attached (check with Exists())
|
|
1565
1655
|
EaseIn(T: Number) -> Number
|
|
@@ -1579,11 +1669,11 @@ Vector.RotateByEuler(Yaw: Number, Pitch: Number, Roll: Number) -> Vector // Rota
|
|
|
1579
1669
|
Vector.ToYawPitch() -> Vector // Treat this vector as a direction: returns Vector(yaw, pitch, 0) in degrees. Yaw 0 = +X (counter-clockwise toward +Y), pitch positive = up. Zero-length input gives (0, 0, 0)
|
|
1580
1670
|
YawPitchToDirection(Yaw: Number, Pitch: Number) -> Vector // Unit direction from aim angles in degrees (inverse of ToYawPitch): yaw 0 = +X, pitch positive = up
|
|
1581
1671
|
SetDefaultFont(Font: Object) // World default font for all UI text/buttons that don't set one (e.g. Ori.font.google.Fredoka); None reverts to the engine default (Nunito)
|
|
1582
|
-
SetModelAnchor(Model: Object, AnchorName: Text, LocalPosition: Position, LocalRotationEulerDegrees: Vector) // Weapon sits wrong in the hand / anchor misplaced -> define or replace any named anchor ("grip", "muzzle", custom): model-root local, cm Z-up, rotation maps anchor axes (+X = forward) into model axes; wins over the asset's own anchors, call before or after EquipWeapon
|
|
1672
|
+
SetModelAnchor(Model: Object, AnchorName: Text, LocalPosition: Position, LocalRotationEulerDegrees: Vector) // Weapon sits wrong in the hand / anchor misplaced -> define or replace any named anchor ("grip", "muzzle", custom): model-root local, cm Z-up, rotation maps anchor axes (+X = forward) into model axes; wins over the asset's own anchors, call before or after EquipWeapon; a grip anchor seats BOTH the third-person hold and the EnableLocalWeaponView viewmodel
|
|
1583
1673
|
SwitchStage(Name: Text) // `#stage` projects only: switch to another stage of this project (multiplayer-safe; all stages ship in the project source, so Name may be dynamic). Use LoadWorldTransferPush before switching to carry values across.
|
|
1584
|
-
Object.SetRenderOffset(OffsetCm: Vector, RotationEulerDegrees: Vector, Scale: Number) // Visual-only offset on top of the sim transform (bob/lean/sway); colliders/AI/queries unaffected. OffsetCm shifts in WORLD space (not rotated into the object's local frame); rotation composes after the object's own
|
|
1674
|
+
Object.SetRenderOffset(OffsetCm: Vector, RotationEulerDegrees: Vector, Scale: Number) // World/display/player objects only (crowds/instance batches reject). Visual-only offset on top of the sim transform (bob/lean/sway); colliders/AI/queries unaffected. OffsetCm shifts in WORLD space (not rotated into the object's local frame); rotation composes after the object's own and pivots at the object CENTER (SetRotation pivots at origin = ref-box bottom-center). Call per-frame for procedural animation; ClearRenderOffset resets
|
|
1585
1675
|
Object.ClearRenderOffset() // Remove this object's visual render offset
|
|
1586
|
-
Object.RaycastObject(Origin: Position, Direction: Vector, MaxDistance: Number) -> Object // Nearest object the ray hits within MaxDistance (this object excluded), or a None-like Object on miss (check Exists()). Hits anything solid incl. untagged walls/floor/terrain - the go-to for projectile impact and line-of-sight tests. Direction need not be normalized
|
|
1676
|
+
Object.RaycastObject(Origin: Position, Direction: Vector, MaxDistance: Number) -> Object // Nearest object the ray hits within MaxDistance (this object excluded), or a None-like Object on miss (check Exists()). Hits anything solid incl. untagged walls/floor/terrain - the go-to for projectile impact and line-of-sight tests. Direction need not be normalized; fast projectiles: raycast the segment travelled this frame (origin = previous position, MaxDistance = the step) - positional overlap checks skip thin targets between frames
|
|
1587
1677
|
Object.RaycastObjectWithTag(Origin: Position, Direction: Vector, MaxDistance: Number, Tag: Number) -> Object // RaycastObject limited to objects carrying Tag
|
|
1588
1678
|
Object.RaycastObjectIgnoreTag(Origin: Position, Direction: Vector, MaxDistance: Number, IgnoreTag: Number) -> Object // RaycastObject that skips objects carrying IgnoreTag (e.g. ignore pickups/projectiles)
|
|
1589
1679
|
Object.RaycastPosition(Origin: Position, Direction: Vector, MaxDistance: Number) -> Position // Impact point of RaycastObject's ray; on miss returns Origin + Direction.GetNormal() * MaxDistance
|
|
@@ -1595,11 +1685,11 @@ TileGridHasLineOfSight(Grid: List, StartX: Number, StartY: Number, StartZ: Numbe
|
|
|
1595
1685
|
TileGridSmoothPath(Grid: List, Path: List) -> List
|
|
1596
1686
|
Object.SetTerrainGrassVisible(Visible: Bool) // Terrain only; false = no grass on this terrain (deserts/cities/moon), true = grass even while the terrain object is hidden, and counts as grass intent (enables the auto ground tint = full meadow look). Unset default: sparse untinted grass following terrain visibility. Same as declarative grass_visible
|
|
1597
1687
|
SpawnEffectAimed(Effect: Object, Position: Position, Direction: Vector) -> Object // Spawns with the effect's authored up/emission axis aimed along Direction (e.g. exhaust: SpawnEffectAimed(fx, GetPosition(), -GetFacingDirection())). Zero direction = unrotated.
|
|
1598
|
-
Object.SetEmitterLightsEnabled(Enabled: Bool)
|
|
1688
|
+
Object.SetEmitterLightsEnabled(Enabled: Bool) // Toggle the lights this object's mesh bakes via emitter(...) sockets (default on) - e.g. lamps dark at day
|
|
1599
1689
|
Object.GetEmitterLightsEnabled() -> Bool
|
|
1600
1690
|
SetFogSkyboxAffect(Percent: Number) // Default: 0 (sky ignores distance fog). 100 = distant sky fully fogged, ~30 = horizon haze
|
|
1601
1691
|
TileGridSetWalkableFromStaticColliders(Grid: List, OriginTile: Vector, ZMin: Number, ZMax: Number)
|
|
1602
|
-
Object.SetCursorUiMode(On: Bool)
|
|
1692
|
+
Object.SetCursorUiMode(On: Bool) // true: release mouse-look AND show the cursor so players can click UI (menus/shops); false: restore gameplay look. Prefer over toggling SetIsMouseLookOn/SetMouseVisible separately.
|
|
1603
1693
|
Object.SetBoneRotation(BoneName: Text, EulerDegrees: Vector) // Replace the named bone(...) rig joint's local rotation (Euler degrees Z-up, #clip convention); wins over the playing clip for that bone until cleared. Max 16 bones per object. Display-only: colliders/physics unaffected. Call per-frame in forever{} for tails, breathing, head tracking
|
|
1604
1694
|
Object.ClearBoneOverrides() // Remove all of this object's bone rotation overrides (SetBoneRotation, Mix, and BoneLookAt)
|
|
1605
1695
|
Object.SetBoneRotationMix(BoneName: Text, EulerDegrees: Vector, Weight01: Number, BlendSeconds: Number, Additive01: Number) // Weighted, smoothed bone layer over the playing clip: Weight01 blends toward EulerDegrees, BlendSeconds eases changes, Additive01=1 offsets the clip pose instead of replacing it. On a rig with no clip playing it animates from rest - the fully procedural mode. Max 16 bones per object; display-only
|
|
@@ -1611,6 +1701,73 @@ AnyBasicType.NearestObjectXYWithTagNotOnTeam(Tag: Number, TeamId: Number, MaxDis
|
|
|
1611
1701
|
AnyBasicType.NearestObjectWithTagOnTeam(Tag: Number, TeamId: Number, MaxDistance: Number) -> Object
|
|
1612
1702
|
AnyBasicType.NearestObjectXYWithTagOnTeam(Tag: Number, TeamId: Number, MaxDistance: Number) -> Object
|
|
1613
1703
|
AnyBasicType.GetObjectsWithinDistanceWithTagNotOnTeam(Distance: Number, Tag: Number, TeamId: Number) -> List // Tag-carriers with team != TeamId within Distance cm (AoE damage lists); for single-target picks prefer NearestObjectWithTagNotOnTeam
|
|
1704
|
+
GetRayHitPosition(Position: Position, Direction: Vector) -> Position // Unfiltered: first hit of anything (dynamic + static). No max-distance param (range-gate on GetRayHitDistance); on miss returns Position + Direction * 1000000.
|
|
1705
|
+
GetRayHitObject(Position: Position, Direction: Vector) -> Object // Unfiltered: first hit object. Returns null Object on miss (check .Exists()).
|
|
1706
|
+
GetRayHitStaticPosition(Position: Position, Direction: Vector) -> Position // Unfiltered: first STATIC hit (terrain, walls). No max-distance param (range-gate on GetRayHitDistance); on miss returns Position + Direction * 1000000.
|
|
1707
|
+
GetRayHitStaticObject(Position: Position, Direction: Vector) -> Object // Unfiltered: first static hit object. Returns null Object on miss (check .Exists()).
|
|
1708
|
+
Object.GetAnchorWorldPos(AnchorName: Text) -> Position // World position of a socket/anchor on THIS object's own mesh (declare socket("Name", ...) in its #mesh), scaled with the object — unlike AttachToAnchor offsets, this follows object scale. Bone-bound sockets resolve the rest pose. Errors when the anchor doesn't exist.
|
|
1709
|
+
Object.GetAnchorWorldDir(AnchorName: Text) -> Vector // Anchor local +X (forward) rotated to world space; unit vector, unaffected by object scale. The socket axis: parameter is the anchor's local +Z (see GetAnchorWorldUp).
|
|
1710
|
+
Object.GetAnchorWorldUp(AnchorName: Text) -> Vector // Anchor local +Z (the socket's declared axis:) rotated to world space; unit vector, unaffected by object scale.
|
|
1711
|
+
IsWorldReady() -> Bool // true once the current load's scene OnSpawned batch has fully started (see event OnWorldReady); false again from the start of a new load/restart. Poll it before placing things on procedurally built ground/rooms.
|
|
1712
|
+
SetShadowDistance(DistanceMeters: Number) // Crisp sun-shadow range in meters; past it only static geometry casts (coarse cached shadows to ~2km, desktop). Default: 50 (character scale); building-onto-building / city blocks need 200-300. Cost: shadow edges get chunkier as range grows
|
|
1713
|
+
SetDebugRenderMode(Mode: Number) // Diagnostic view: renders ONE lighting/material component ungraded instead of the shaded frame, to tell wrong albedo from wrong light from wrong geometry. 0 off, 1 albedo, 2 normal, 3 material ao, 4 ssao, 5 ao*ssao (what ambient is multiplied by - 0 means no ambient can reach the surface), 6 direct sun, 7 ambient/sky, 8 shadow mask, 9 roughness, 10 metallic, 11 emissive, 12 local lights. Ship worlds with 0; the playtest/live_screenshot `debug_view` parameter is the per-capture equivalent. Default: 0
|
|
1714
|
+
PersistClear()
|
|
1715
|
+
Object.SetInstance(Index: Number, Offset: Vector, RotationEuler: Vector, Size: Vector, ColorRGB: Vector) // Batch only. Overwrites the instance at Index (0..GetInstanceCount-1); args like AddInstance. Errors on out-of-range Index
|
|
1716
|
+
Object.RemoveInstance(Index: Number) // Batch only. Swap-remove: the LAST instance moves into Index, count shrinks by 1. Errors on out-of-range Index
|
|
1717
|
+
SetSkyboxColor(Color: Vector) // Procedural sky only. Base color multiplied into the sky bake before grading (alien/stylized skies); SetSkyboxTint post-multiplies the graded display instead. Default: (1, 1, 1)
|
|
1718
|
+
GetRayHitDistance(Position: Position, Direction: Vector, Tag: Number) -> Number // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Distance in cm to the first hit, -1 on miss — range gates and damage falloff.
|
|
1719
|
+
GetRayHitDistance(Position: Position, Direction: Vector) -> Number // Unfiltered. Distance in cm to the first hit, -1 on miss — range gates and damage falloff; prefer this over measuring to GetRayHitPosition, whose miss sentinel is Position + Direction * 1000000.
|
|
1720
|
+
SetSkyboxCloudType(Type: Number) // Procedural sky only. Cloud vertical form. Default: 0.35; 0 = flat haze layer, 1 = towering cumulus
|
|
1721
|
+
CreateStaticInstanceBatch(Model: Object, Origin: Position) -> Object // Display-only STATIC scatter batch (see <procedural_generation>); Model: Ori.model.* (basics like Ori.model.cone work) or Ori.alias.model.* - mesh models ONLY (effects/images/spawned Objects are rejected at runtime). Max 128 live per world - Destroy() frees. Never mutate per frame - each edit (SetBatchOrigin included) re-uploads the whole batch. Sway/shimmer: vertex #shader on the model's material; instances that truly move: objects or #particles
|
|
1722
|
+
GetRayHitPart(Position: Position, Direction: Vector, Tag: Number) -> Text // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Names the hit volume posed by the target's current animation: "head"/"torso"/"pelvis"/"upper_arm_l"/"forearm_r"/"thigh_l"/"shin_r"/...; "" on miss, rigless target, or a hit outside every volume (treat as a plain body hit) — gate damage on GetRayHitObject, use the part for multipliers.
|
|
1723
|
+
GetRayHitPart(Position: Position, Direction: Vector) -> Text // Unfiltered twin: same part names and "" semantics as GetRayHitPart.
|
|
1724
|
+
TileGridFlowFieldBuild(Grid: List, GoalTiles: List) -> List
|
|
1725
|
+
Object.StartTileFlowFollow(Field: List, Grid: List, OriginTile: Vector, Speed: Number) -> Bool
|
|
1726
|
+
TilePathFollowTakeBlocked() -> List
|
|
1727
|
+
Object.SetSubmeshTint(SubmeshIndex: Number, TintRGB: Vector) // THIS object's per-submesh tint (faction colors): same LOD-flattened slot space as SetAssetSubmeshTint; multiplies SetColor and the asset tint (black kills). Tint (1,1,1) clears the slot. Skinned models fully supported; tinted statics leave the LOD-collapse fast path; InstanceBatch/TileMap handles reject
|
|
1728
|
+
SetFogOfWarEnabled(Enabled: Bool) // RTS fog of war: per-TEAM vision grid sized from the largest terrain (runtime error without one). Each viewer sees own-team vision; unexplored = black, explored-but-unseen = grey memory (objects hidden), visible = clear. Radius discs only (no line-of-sight blocking); audio is not gated. Enable once at world start; false clears everything. No default sight: until Object.SetSightRadius lands on TEAMED objects, teamed viewers see a near-black stage - grant sight in the same change as the enable
|
|
1729
|
+
Object.SetSightRadius(RadiusCm: Number) // THIS object provides fog vision to ITS team (GetTeam) in a RadiusCm disc, refreshed ~every 8 ticks while alive; 0 unregisters. Teamless objects provide no vision. Give it to units/buildings after SetFogOfWarEnabled
|
|
1730
|
+
IsPositionVisibleToTeam(Position: Position, TeamId: Number) -> Bool // True when the position is currently in TeamId's live vision (explored-only memory returns false). Unset/no-team viewers see everything; a team that never held vision sees nothing. Fog disabled = always true. For AI/scripting; rendering hides automatically
|
|
1731
|
+
GetTerrainBoundsMin() -> Position // World-space min corner of the LARGEST terrain (z = lowest height sample). Errors when no terrain exists. Fog and the minimap snapshot use the same terrain.
|
|
1732
|
+
GetTerrainBoundsMax() -> Position // World-space max corner of the LARGEST terrain (z = highest height sample). Minimap math: uv = (P - Min) / (Max - Min).
|
|
1733
|
+
GetRayHitNormal(Position: Position, Direction: Vector, Tag: Number) -> Vector // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Unit surface normal at the hit (world axes; box = face normal, sphere = radial); zero Vector on miss.
|
|
1734
|
+
GetRayHitNormal(Position: Position, Direction: Vector) -> Vector // Unfiltered twin: same normal and zero-Vector miss semantics as GetRayHitNormal.
|
|
1735
|
+
Object.SetSavedToWorld(IsOn: Bool) // Script-spawned objects are TRANSIENT: saving the world never writes them into the scene text (your Spawn code recreates them). SetSavedToWorld(true) makes this one save like an editor-placed object; editor-placed objects always save
|
|
1736
|
+
ArcToTarget(From: Position, To: Position, FlightSeconds: Number, GravityCmS2: Number) -> List // Arc handle leaving From and passing through To exactly at FlightSeconds under GravityCmS2 (positive cm/s^2, e.g. 980; captured in the handle - world SetGravity is NOT used). Query with ArcPositionAt/ArcVelocityAt/ArcFlightSeconds.
|
|
1737
|
+
ArcToTargetWithPeak(From: Position, To: Position, PeakHeightCm: Number, GravityCmS2: Number) -> List // As ArcToTarget but sized by apex height above From (the 'make it float higher' knob); errors when PeakHeightCm can't reach the target height. Read the flight time back with ArcFlightSeconds.
|
|
1738
|
+
ArcFlightSeconds(Arc: List) -> Number // Seconds from launch until the arc reaches its target - the contact time to hand to MoveToArriveIn / PlayAnimationHittingContactIn.
|
|
1739
|
+
ArcPositionAt(Arc: List, Seconds: Number) -> Position // Closed-form arc sample at Seconds. Drive the ball each frame with SetPosition(ArcPositionAt(arc, t)) - no drift, no lerp seams between phases. Past the flight time it keeps extrapolating downward; stop driving it yourself.
|
|
1740
|
+
ArcVelocityAt(Arc: List, Seconds: Number) -> Vector // Arc velocity at Seconds: face the object along travel, or reflect it on a block/bounce.
|
|
1741
|
+
GetAnimationLeadSeconds(AnimationAsset: Object, ContactPercent: Number) -> Number // Seconds from clip start to ContactPercent at normal playback - how early PlayAnimationHittingContactIn can fire. Accounts for SetAnimationAssetSpeedFactor; never derive this from GetAnimationDuration by hand.
|
|
1742
|
+
Object.PlayAnimationHittingContactIn(Animation: Object, ContactPercent: Number, SecondsUntilContact: Number) -> Bool // Call every frame with the remaining seconds: false while too early, then plays Animation rate-scaled (0.5x..2x, skipping into the clip when already late) so ContactPercent lands exactly SecondsUntilContact from now, and true from then on. Will not restart a clip it already started, so no 'started' guard var - use a dedicated action clip, not one that may already be playing.
|
|
1743
|
+
Object.MoveToArriveIn(Target: Position, SecondsRemaining: Number) // One frame of movement sized to arrive at Target in SecondsRemaining - recompute the remainder each frame like the other continuous movers (a constant argument eases out and never lands; 0 or negative arrives this frame). Auto-move animations see the speed, so characters run instead of sliding. On gravity characters Z is discarded (as with MoveTowardContinuous).
|
|
1744
|
+
Object.MoveToArriveInFacing(Target: Position, SecondsRemaining: Number) // MoveToArriveIn, turning the object's forward (+X) along the movement direction.
|
|
1745
|
+
Object.AttachToAnchor(Parent: Object, ParentAnchor: Text, ChildAnchor: Text) // Mounts the CHILD's named socket onto the parent's socket (position + orientation) - the part pivots at its socket instead of its center
|
|
1746
|
+
GetAllObjectsWithTagOnTeam(Tag: Number, TeamId: Number) -> List // GetAllObjectsWithTag filtered to TeamId (SetTeam / scene team =; unset objects never match). Same allocate-per-call advice
|
|
1747
|
+
AdvertiseSession() // List this session in the project's public session browser (hosts can also toggle this in the engine session panel; last write wins). Not a replication/server-authority API.
|
|
1748
|
+
StopAdvertisingSession() // Unlist this session (it stays joinable via share link).
|
|
1749
|
+
ListSessions() // Request the project's listed sessions (identity automatic, exact content version). Results update Session* getters and fire OnSessionListUpdated; state 2 with 0 sessions = nobody hosts this exact version.
|
|
1750
|
+
JoinSession(RelayPubkey: Text) // Join a listed session by SessionRelayPubkey; deterministic shared sim still applies after joining.
|
|
1751
|
+
SessionCount() -> Number // Number of listed sessions from the latest ListSessions result.
|
|
1752
|
+
SessionRelayPubkey(Index: Number) -> Text // Relay pubkey for a listed session; pass to JoinSession.
|
|
1753
|
+
SessionConnectId(Index: Number) -> Text // Debug/connect id for a listed session; usually not needed for gameplay code.
|
|
1754
|
+
SessionDisplayName(Index: Number) -> Text // Host display name for a listed session.
|
|
1755
|
+
SessionRefreshState() -> Number // Latest list state: 0 idle, 1 pending, 2 ok, 3 error (read SessionErrorText).
|
|
1756
|
+
SessionStage(Index: Number) -> Text // #stage name a listed session is currently on (empty for stageless projects); show or filter it in a browser.
|
|
1757
|
+
SessionErrorText() -> Text // Reason for state 3 (empty otherwise), e.g. relay unreachable or no loaded world.
|
|
1758
|
+
SessionPlayerCount(Index: Number) -> Number // Connected player count for a listed session (0 = unknown).
|
|
1759
|
+
OpenSessionBrowser() // Open the engine's built-in session browser UI (no-op in headless/validation).
|
|
1760
|
+
Object.ShatterAndDestroy() // Destroy() plus a debris burst from the model's baked chunk set (needs `#mesh Name destructible: <word>`; without it, plain Destroy()). Debris is display-only - no sim collision.
|
|
1761
|
+
TerrainSlopeAtXY(X: Number, Y: Number) -> Number // slope01 (g/(1+g); 0.5 = 45deg) of topmost terrain at world XY; 1 if no terrain (void = unwalkable). Walkers wedge above ~0.5
|
|
1762
|
+
TileGridSetWalkableFromTerrain(Grid: List, OriginTile: Vector, MaxSlope01: Number)
|
|
1763
|
+
TileGridSetEdgesFromTerrain(Grid: List, OriginTile: Vector, MaxStepCm: Number)
|
|
1764
|
+
TileGridStampRect(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number, Blocked: Bool)
|
|
1765
|
+
TileGridStampObject(Grid: List, OriginTile: Vector, Obj: Object, Blocked: Bool)
|
|
1766
|
+
TileGridIsReachable(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number) -> Bool
|
|
1767
|
+
TileGridIsReachable(Grid: List, X1: Number, Y1: Number, Z1: Number, X2: Number, Y2: Number, Z2: Number) -> Bool
|
|
1768
|
+
TileGridBakeCrowdObstacles(Grid: List, OriginTile: Vector)
|
|
1769
|
+
TerrainHeightsFromTiers(Heights: List, SamplesX: Number, SamplesY: Number, Tiers: Text, TierCm: Number) // REPLACE Heights from an ASCII tier map (digits = tier * TierCm, '.' = 0, 'r' = ramp; first row = max Y). See <terrain_generation>
|
|
1770
|
+
RaiseWorldError(Message: Text) // Fail-loud assert: raises a Weave runtime error carrying Message (validation surfaces it; in play the program pauses like any script error)
|
|
1614
1771
|
|
|
1615
1772
|
</built_in_function_list>
|
|
1616
1773
|
|
|
@@ -1619,7 +1776,7 @@ AnyBasicType.GetObjectsWithinDistanceWithTagNotOnTeam(Distance: Number, Tag: Num
|
|
|
1619
1776
|
- X/Y are in cell units: scale world coords down (e.g. Fbm2(pos.X / 800, pos.Y / 800, 4, 7)); features repeat at integer-cell scale. Use a different Seed per layer (trees vs rocks).
|
|
1620
1777
|
- SpawnTerrainFromHeights spawns a collidable terrain heightfield from code; heights are row-major (iy * SamplesX + ix), in cm relative to Position.Z. Terrain XY is centered on Position.
|
|
1621
1778
|
- terrain.GetTerrainHeightAt(X, Y) returns the world-space surface Z at world (X, Y) (bilinear); errors if the caller has no heightfield or (X, Y) is outside it.
|
|
1622
|
-
- Terrain grass: every terrain grows sparse built-in grass automatically; grass_visible = false / SetTerrainGrassVisible(false) removes it entirely (deserts, cities). The ground under the blades is the terrain's own splat/material — style it with `#terrain_splat` rules or terrain layers. Set from code via terrain.SetTerrainGrass / terrain.SetTerrainGrassWeightmap / terrain.SetTerrainGrassHeightCm / terrain.SetTerrainGrassStyle / terrain.SetTerrainGrassVisible (all per-terrain), or declaratively on terrain blocks (grass_model = "mesh.X", grass_material = "material.X", grass_weightmap = "image.X" or a #texture color, grass_height_cm = 28, grass_visible = true/false, grass_style = "blades"). No model = built-in grass: cheap alpha-cut tuft cards by default, or geometry blades via SetTerrainGrassStyle(1) (heavier, best with SetGrassSpacingCm 8..15 for dense hero fields). Custom #mesh blade budget: tiny tuft (<=100 verts), single material slot, bottom-anchored at Z=0; blade size comes from authoring the mesh at the right cm size, then grass_height_cm rescales it (28 = as-authored). Global tuning calls: SetGrassSpacingCm, SetGrassTerrainActiveDistanceM, SetGrassCullDistanceM, SetGrassFadeDistancePercent, SetGrassFarThinningPower; look calls: SetGrassRootColor, SetGrassTipColor, SetGrassBrightnessVariationPercent, SetGrassSizeVariationPercent, SetGrassHeightMinCm, SetGrassHeightMaxCm, SetGrassBladeWidthPercent, SetGrassWavePeriodCm, SetGrassWindStrengthCm, SetGrassWindSpeedPercent.
|
|
1779
|
+
- Terrain grass: every terrain grows sparse built-in grass automatically; grass_visible = false / terrain.SetTerrainGrassVisible(false) removes it entirely (deserts, cities). The ground under the blades is the terrain's own splat/material — style it with `#terrain_splat` rules or terrain layers. Set from code via terrain.SetTerrainGrass / terrain.SetTerrainGrassWeightmap / terrain.SetTerrainGrassHeightCm / terrain.SetTerrainGrassStyle / terrain.SetTerrainGrassVisible (all per-terrain), or declaratively on terrain blocks (grass_model = "mesh.X", grass_material = "material.X", grass_weightmap = "image.X" or a #texture color, grass_height_cm = 28, grass_visible = true/false, grass_style = "blades"). No model = built-in grass: cheap alpha-cut tuft cards by default, or geometry blades via SetTerrainGrassStyle(1) (heavier, best with SetGrassSpacingCm 8..15 for dense hero fields). Custom #mesh blade budget: tiny tuft (<=100 verts), single material slot, bottom-anchored at Z=0; blade size comes from authoring the mesh at the right cm size, then grass_height_cm rescales it (28 = as-authored). Global tuning calls: SetGrassSpacingCm, SetGrassTerrainActiveDistanceM, SetGrassCullDistanceM, SetGrassFadeDistancePercent, SetGrassFarThinningPower; look calls: SetGrassRootColor, SetGrassTipColor, SetGrassBrightnessVariationPercent, SetGrassSizeVariationPercent, SetGrassHeightMinCm, SetGrassHeightMaxCm, SetGrassBladeWidthPercent, SetGrassWavePeriodCm, SetGrassWindStrengthCm, SetGrassWindSpeedPercent.
|
|
1623
1780
|
- The same noise is available in #shader WGSL: ori_hash2(x, y, seed), ori_value_noise2(x, y, seed), ori_fbm2(x, y, octaves, seed) (f32; seed/octaves u32/i32), ori_hash2_cell(cx, cy, seed) -> u32. Matches the Weave functions exactly at integer lattice points, so sim placement and shader visuals can share one layout.
|
|
1624
1781
|
Example (hills + deterministic tree scatter):
|
|
1625
1782
|
#class WorldGen
|
|
@@ -1653,6 +1810,8 @@ event OnSpawned {
|
|
|
1653
1810
|
- Flat gameplay areas: TerrainHeightsFlattenDisc/Rect/Path AFTER noise/erosion so nothing roughens them again. ThermalErode + RemapCurve make raw noise look natural (erode first, then remap for plateaus/cliffs).
|
|
1654
1811
|
- Prefer 129..257 samples per axis (513 max; big lists inflate sim state — generate in OnSpawned local vars so they are GC'd after spawn).
|
|
1655
1812
|
- GroundHeightAtXY(x, y) -> world Z cm of the topmost ground at world XY (downward raycast over terrain + static colliders; 0 if nothing). Use for placing props/villages on generated ground; works immediately after SpawnTerrainFromHeights in the same event.
|
|
1813
|
+
- TerrainSlopeAtXY(x, y) -> slope01 of the topmost terrain at world XY (0.5 = 45deg; no terrain = 1, treat void as unwalkable). Slopes steeper than ~0.5 silently immobilize walkers - keep unit routes below it or block those tiles/edges in the nav grid (see <tile_pathfinding>).
|
|
1814
|
+
- TerrainHeightsFromTiers(Heights, SamplesX, SamplesY, Tiers, TierCm) REPLACES the field with plateau heights from an ASCII tier map in a triple-quoted Text (digits 0-9 = tier, h = tier * TierCm; '.' = 0; 'r' = ramp graded toward its lower neighbor; rows = lines, FIRST row = MAX Y / north). Hand-drawn RTS cliff maps: one char per nav tile, then TileGridSetEdgesFromTerrain(MaxStepCm = TierCm/3) derives the cliff edges; compose Smooth/Erode/splat after.
|
|
1656
1815
|
- Realism combo: Warp after base noise (kills grid-straight lines), then ErosionFilter (fast stateless gullies/ridges; run AFTER RemapCurve so the detail lands at final scale, and its look continues sub-cell on the GPU display surface). Avoid HydraulicErode/ThermalErode by default — slow iterative CPU sims; reach for HydraulicErode only when routed drainage or its flow/deposit masks are truly needed. MaskFromFlow gives a wetness mask WITHOUT eroding.
|
|
1657
1816
|
- Terrain-following rivers: FlowPath traces downhill from a spring cell into a CarvePath-ready point list (stops at sea level = MinHeightCm). Check PathXYOut has >= 4 numbers (2 points) before carving.
|
|
1658
1817
|
- Roads that climb: GradePath lerps the flatten height start->end along the path (use terrain heights at the endpoints); FlattenPath only does constant height.
|
|
@@ -1717,20 +1876,20 @@ Two terrain display types:
|
|
|
1717
1876
|
</terrain_generation>
|
|
1718
1877
|
|
|
1719
1878
|
<procedural_generation_batches>
|
|
1720
|
-
- InstanceBatch: thousands of decorative copies of one model as ONE display object (
|
|
1879
|
+
- InstanceBatch: thousands of decorative copies of one model as ONE display object (pebbles, rubble, grass tufts). Instances have no physics/logic/picking and are invisible to the editor - never clickable, movable, or saved there; the layout lives in this code (edit by index via SetInstance/RemoveInstance, or ClearInstances + re-add). Identical static meshes already render as one instanced draw either way - a batch saves per-object sim/load cost (spawn, rollback state, join checkpoints), not draw calls. Copies a human should hand-place or that need any interactivity: SpawnObject. Drifting petals/leaves/debris: #particles mesh components, not a batch. Animated look on scatter (sway/shimmer): vertex #shader on the model's material - never per-frame batch edits (SetBatchOrigin included), and batch handles reject Object.* calls like SetRenderOffset.
|
|
1880
|
+
- Batches outlive their creator; only Destroy() frees a slot (128 live max). One batch per model kind, edited in place - don't create per region or per round.
|
|
1721
1881
|
- Model can be a basic primitive (Ori.model.cube/cone/wedge/...); add SetBatchMaterial(Material.X) for textured scatter without any market asset.
|
|
1722
|
-
Example (
|
|
1723
|
-
var
|
|
1724
|
-
for i in 0..
|
|
1725
|
-
|
|
1726
|
-
|
|
1882
|
+
Example (2000 pebbles):
|
|
1883
|
+
var pebbles = CreateStaticInstanceBatch(Ori.alias.model.pebble, Origin())
|
|
1884
|
+
for i in 0..2000 {
|
|
1885
|
+
var s = 6 + Hash2(i, 3, 5) * 14
|
|
1886
|
+
pebbles.AddInstance(Vector((Hash2(i, 0, 5) - 0.5) * 4000, (Hash2(i, 1, 5) - 0.5) * 4000, 0),
|
|
1887
|
+
Vector(0, 0, Hash2(i, 2, 5) * 360), Vector(s, s, s * 0.7), Vector(0.62, 0.6, 0.55))
|
|
1727
1888
|
}
|
|
1728
1889
|
</procedural_generation_batches>
|
|
1729
1890
|
|
|
1730
1891
|
<ternary>
|
|
1731
|
-
-
|
|
1732
|
-
- If(cond, a, b) -> T
|
|
1733
|
-
- Only the taken branch is evaluated
|
|
1892
|
+
- No ?: operator - use If(cond, a, b) -> T; only the taken branch is evaluated
|
|
1734
1893
|
</ternary>
|
|
1735
1894
|
|
|
1736
1895
|
<events>
|
|
@@ -1745,6 +1904,8 @@ for i in 0..3000 {
|
|
|
1745
1904
|
- All OnPreSpawn finish for the frame before the first OnSpawned runs
|
|
1746
1905
|
- Ordering between objects' OnPreSpawn is unspecified
|
|
1747
1906
|
- GameCommand fires on every class that declares it when a user sends a command text message (headless build orders or chat-driven commands). Event.Text is the raw command line, Event.Sender the sender's player object. Parse Text yourself (e.g. "build house 3 4").
|
|
1907
|
+
- OnWorldReady fires once per load (again after RestartGame) when every scene object's OnSpawned has STARTED (run to its first yield), spilled load batches included. Poll IsWorldReady() for spawn/placement code that depends on procedurally built ground or rooms (terrain, BuildRoom) instead of Wait() timers; a handler that Wait()s mid-body may still be mid-flight at ready.
|
|
1908
|
+
- OnPlayerLeft fires on the leaver's player object the moment IsConnected() flips false (the body stays ~90s for reconnect; OnDestroy only at grace expiry): clear SetHoverText labels / roster rows here. OnPlayerReconnected fires when the SAME body resumes within the grace; a post-grace rejoin is a fresh spawn (OnSpawned).
|
|
1748
1909
|
</events>
|
|
1749
1910
|
<event_list>
|
|
1750
1911
|
event OnPreSpawn
|
|
@@ -1753,7 +1914,7 @@ event OnDestroy
|
|
|
1753
1914
|
event WasAttacked
|
|
1754
1915
|
Event.Attacker .. Object
|
|
1755
1916
|
Event.Damage .. Number
|
|
1756
|
-
event
|
|
1917
|
+
event OnSessionListUpdated
|
|
1757
1918
|
event GameCommand
|
|
1758
1919
|
Event.Text .. Text
|
|
1759
1920
|
Event.Sender .. Object
|
|
@@ -1775,8 +1936,10 @@ event ClickThisObject
|
|
|
1775
1936
|
Event.Presser .. Object
|
|
1776
1937
|
event ClickThisUI
|
|
1777
1938
|
Event.Presser .. Object
|
|
1939
|
+
Event.ClickFraction .. Vector
|
|
1778
1940
|
event ClickThisButton
|
|
1779
1941
|
Event.Presser .. Object
|
|
1942
|
+
Event.ClickFraction .. Vector
|
|
1780
1943
|
event ClickButton
|
|
1781
1944
|
Event.ButtonClicker .. Object
|
|
1782
1945
|
Event.ClickedButton .. Object
|
|
@@ -1882,6 +2045,9 @@ event InventoryDropOnWorld
|
|
|
1882
2045
|
Event.SlotIndex .. Number
|
|
1883
2046
|
Event.DropObject .. Object
|
|
1884
2047
|
Event.DropPosition .. Position
|
|
2048
|
+
event OnWorldReady
|
|
2049
|
+
event OnPlayerLeft
|
|
2050
|
+
event OnPlayerReconnected
|
|
1885
2051
|
|
|
1886
2052
|
</event_list>
|
|
1887
2053
|
|
|
@@ -1905,7 +2071,7 @@ event InventoryDropOnWorld
|
|
|
1905
2071
|
</3. Physics step & motion application>
|
|
1906
2072
|
<Single-frame overwrite rule>
|
|
1907
2073
|
- See <movement_laws>: one continuous translation + one rotation per tick, last call wins.
|
|
1908
|
-
- Jump() and gravity own a character's vertical velocity: MoveWithVelocityContinuously on a gravity-affected character never writes Z at all (the component is discarded), so scripted vertical motion there needs
|
|
2074
|
+
- Jump() and gravity own a character's vertical velocity: MoveWithVelocityContinuously on a gravity-affected character never writes Z at all (the component is discarded), so scripted vertical motion there needs EnableCharacterGravity(false) first. On DYNAMIC bodies a same-tick SetPhysicsVelocity built from a velocity read taken BEFORE a JumpInDirection writes the old Z back and the jump never lifts - re-read velocity after the jump, or keep script movement XY-only and let physics own Z.
|
|
1909
2075
|
</Single-frame overwrite rule>
|
|
1910
2076
|
<Readback semantics>
|
|
1911
2077
|
- Within the same tick, GetPosition()/GetRotation() return the pre-physics values. Updated transforms become visible to script on the next tick.
|
|
@@ -1913,6 +2079,7 @@ event InventoryDropOnWorld
|
|
|
1913
2079
|
<Notes>
|
|
1914
2080
|
- OnSpawned ordering across different objects is unspecified.
|
|
1915
2081
|
- Per-object forever/Update brains are the right default. Past a few hundred always-active objects the per-object dispatch itself dominates the frame - switch to one manager forever loop calling into a kept List of members (u.Tick() per member; ~8x cheaper at 600 objects), and for per-tick targeting call NearestObjectWithTagNotOnTeam(Tag, MyTeam, Range) instead of looping a GetAllObjectsWithTag list per object; if you must scan a list, rescan on a ~0.25s staggered cooldown, not every tick.
|
|
2082
|
+
- Wait()/forever parks a per-object saved continuation (every handler local + call scratch) against one global 262,144-slot budget - hundreds of live instances of one class exhaust it ('continuation slot capacity exceeded' freeze). Swarm classes (pickups, projectiles): use event Update + class vars (a returning handler parks nothing).
|
|
1916
2083
|
- 1000+ active units: regular colliding objects cap near ~500 (collision pairs). Spawn hordes as ClimbingCrowdObject (lean deterministic crowd bodies; steer with SetAsClimbingCrowdTarget) - or SetCanCollide(false) units picking targets via NearestObjectWithTagNotOnTeam on a staggered ~0.25s cooldown.
|
|
1917
2084
|
- Dense pickup/particle fields (hundreds+): every spawned object is replicated multiplayer state, so thousands of individual pickups bloat rollback and read as network lag even at healthy ping. Pool + recycle a capped set (destroy/respawn churn also hitches), keep pickups non-colliding (SetCanCollide(false) - solid ones physically shove players; 33+ colliders stacked at one point also overflow crowd steering), collect via distance checks, and bundle several visuals per synced object when you want thousands on screen.
|
|
1918
2085
|
- See also: <movement_laws> and <time> sections for API-level constraints and examples.
|
|
@@ -1921,21 +2088,23 @@ event InventoryDropOnWorld
|
|
|
1921
2088
|
|
|
1922
2089
|
<ui>
|
|
1923
2090
|
- Use ClickButton event to detect any button click. Event properties: Event.ButtonClicker, Event.ClickedButton
|
|
1924
|
-
- event ClickThisUI fires when any UI element of this class (frame/image/label/button) is clicked; buttons additionally fire ClickThisButton/ClickButton
|
|
2091
|
+
- event ClickThisUI fires when any UI element of this class (frame/image/label/button) is clicked; buttons additionally fire ClickThisButton/ClickButton. Both carry Event.ClickFraction: the 0..1 click position inside the element (x right, y down) — minimaps, sliders, pickers
|
|
1925
2092
|
- Drag-drop: mark elements draggable=true (or SetUiDraggable(true)). Engine handles press+move threshold, drag ghost and click suppression. UiDragStart fires on drag start (Event.Dragger, Event.DragElement); UiDrop on release (Event.Dragger, Event.DragElement, Event.DropElement = UI element under cursor or None, Event.DropObject = world object hit when dropped outside UI, Event.DropPosition). Both broadcast to every class defining the handler (player class or the element's class both work); ClickThisUI instead fires only on the clicked element's own class. A short press without movement still clicks normally.
|
|
1926
2093
|
- Tooltip/cursor-follow UI lagging behind the mouse: never chase the cursor with per-frame offset loops - call SetUiFollowsCursor(true, dx, dy) once (owned screen canvas); the engine glues it to the real cursor. Scripts only refresh its text.
|
|
1927
2094
|
- SetCursorImage(ImageAsset) turns the OS hardware cursor into that image (zero-lag, unlike moving a UI element each frame); SetCursorImage(NONE) restores the stock cursor. Overload SetCursorImage(ImageAsset, HotspotX, HotspotY, Opacity): hotspot is 0..1 of the image ((0,0)=top-left default, (0.5,0.5)=image centered on the pointer - ideal drag ghost), opacity 0..1 scales the image alpha. Don't animate opacity per frame (each step builds a new OS cursor). The image is downscaled to <=64px; touch devices have no cursor, so mobile drag ghosts still need a UI element. Pattern: SetCursorImage(ghost, 0.5, 0.5, 0.8) in UiDragStart, SetCursorImage(NONE) in UiDrop.
|
|
1928
2095
|
- Hit-testing ignores plain images/labels (clicks pass through), BUT any element whose class has script or with draggable=true becomes clickable and blocks hit tests under it. Keep overlay visuals (tooltips, custom drag ghosts) class-free and move them from another class's code, or they will swallow the hover/click you are trying to detect.
|
|
1929
2096
|
- Inventories/hotbars/chests: see <inventory> — item clauses + BindInventory give a fully engine-synced slot UI.
|
|
2097
|
+
- Minimap: an image element with image = "ori.image.engine.minimap" shows an auto-refreshing top-down snapshot of the largest terrain, spanning its XY bounds (terrain only — no objects/fog). Blips/camera rect = child frames placed via GetTerrainBoundsMin/Max; map clicks back to world XY with Event.ClickFraction.
|
|
1930
2098
|
- ui object tags for world description: screenCanvas/worldCanvas/frame/textLabel/button; elements nest
|
|
1931
2099
|
- Element handles in script: scene-declared elements resolve as Scene.X; children of a SpawnCanvas canvas via canvas.FindChildUI("Name")
|
|
2100
|
+
- Spawning saved UI at runtime: canvas-root saved block -> SpawnCanvas(SavedObject.X, pos); element-root block (frame/textLabel/button...) -> SpawnChildUI(SavedObject.X, ParentUI). SpawnObject rejects both.
|
|
1932
2101
|
- worldCanvas = screenCanvas anchored to a world position (position = (x, y, z)): children use the same base-unit offsets around the projected anchor and keep constant on-screen size (nameplates, above-object markers); draws under screenCanvas; move the canvas object to follow things
|
|
1933
2102
|
|
|
1934
2103
|
- UI offset and size uses base unit assuming 1600x900 window size. Resize -> ui scales proportionally. Make layouts work in 1600x900 base unit.
|
|
1935
2104
|
- text align uses words: align=(horizontal, vertical) where horizontal=left|center|right, vertical=top|center|bottom. Defaults: button text is already CENTER, textLabel is top-left; only set align to override (e.g. align=(center, center) to center a label). In script use SetTextAlign(h, v) with 0/1/2 = left/center/right, top/center/bottom.
|
|
1936
2105
|
- Text off-center in a panel: put the textLabel INSIDE the frame with NO size (a size-less child of a plain frame inherits the frame's full rect) + align=(center, center). Never center by hand-tuned offset - text width is not measurable, guessed offsets are always off.
|
|
1937
2106
|
- Fonts: SetFont(Ori.font.google.<Family_Name>) uses any Google Font (fonts.google.com) directly, no upload; underscores for spaces, optional weight/style suffix (e.g. Ori.font.google.Roboto_Mono, Ori.font.google.Lobster, Ori.font.google.Roboto_Mono.700italic). Uploaded font assets use SetFont(Ori.font.<creator>.<name>). UI text without a font uses the world default: SetDefaultFont(Ori.font...) sets it, default Nunito.
|
|
1938
|
-
- Declarative fonts: textLabel/button clauses accept font_size = N (base units, default 16) and font = "font.google.<Family_Name>" / "font.<creator>.<name>" — prefer these over SetFontSize/SetFont calls in OnSpawned.
|
|
2107
|
+
- Declarative fonts: textLabel/button clauses accept font_size = N (base units, default 16) and font = "font.google.<Family_Name>" / "font.<creator>.<name>" — prefer these over SetFontSize/SetFont calls in OnSpawned. Text never auto-shrinks to fit its rect - match font_size to the rect.
|
|
1939
2108
|
|
|
1940
2109
|
- PREFER auto-layout containers over manual offsets: vStack (stack down) / hStack (stack across) / grid (set columns). They self-arrange children, so you don't hand-place each element.
|
|
1941
2110
|
- HUG (recommended default): a vStack/hStack with NO `size` auto-sizes to fit its children + padding + gaps. Just author the children; the container grows to wrap them.
|
|
@@ -1946,12 +2115,16 @@ event InventoryDropOnWorld
|
|
|
1946
2115
|
- padding/gap/margin take N (all sides) or (x, y) with x=left/right, y=top/bottom; per-side: padding_left/right/top/bottom, margin_left/right/top/bottom.
|
|
1947
2116
|
- Word values: child_align = left|center|right (x) or top|center|bottom (y); align_self_x/y = auto|start|center|end|stretch (per-child override of child_align; stretch fills the cross axis).
|
|
1948
2117
|
- frame = plain/absolute container (no auto-layout). Use it for screen-anchored HUD corners and for elements you reposition at runtime via SetUiOffset. A frame/grid without `size` inherits the parent's size. Position a child by ONE of: offset; anchor; insets.
|
|
1949
|
-
- anchor = (x, y): pin the element to a spot on the parent (x=left|center|right, y=top|center|bottom). It also sets the pivot to match, so anchor=(center,center) centers, anchor=(center,bottom) makes a bottom bar, anchor=(right,bottom) a corner. `offset` still nudges. Decouple with pivot=(x,y) (or anchor_x/y + pivot_x/y) when the element's own pivot must differ from the parent attach point.
|
|
2118
|
+
- anchor = (x, y): pin the element to a spot on the parent (x=left|center|right, y=top|center|bottom). It also sets the pivot to match, so anchor=(center,center) centers, anchor=(center,bottom) makes a bottom bar, anchor=(right,bottom) a corner. `offset` still nudges, always in raw screen units (+x right, +y down) regardless of anchor - from a bottom anchor, inward is negative y. Decouple with pivot=(x,y) (or anchor_x/y + pivot_x/y) when the element's own pivot must differ from the parent attach point.
|
|
1950
2119
|
- inset = N | (x, y): pin all four edges to the parent content rect so the child fills parent - inset (per-side: inset_left/inset_right/inset_top/inset_bottom). Use it to inset content inside a button/frame (e.g. an icon: inset=10). insets differ from margin (a child's outer spacing inside a stack/grid) and padding (a container's inner inset applied to ALL its children); a button is not a container, so inset the child rather than padding the button.
|
|
1951
|
-
-
|
|
2120
|
+
- cover = screen (frame): rect = the player's FULL real window instead of the 1600x900 base box - backdrops/vignettes reach wide-monitor edges (unflagged UI lays out in the centered base box, leaving side bands there). Children anchor inside it normally, so a HUD wrapped in a cover frame hugs true screen corners. Owned screen canvases only.
|
|
2121
|
+
- visible = false starts any element hidden (there is no `hidden` field); SetVisible(true) reveals it - same flag.
|
|
2122
|
+
- clip_to_bounds = true clips children (and hit-testing) to this rect; block_input = true blocks clicks passing through. Containers (frame/vStack/hStack/grid) default block_input = TRUE - set block_input = false on overlay panels that must not swallow clicks on the world behind them (other elements default false).
|
|
2123
|
+
- Interactable elements (buttons, classed elements, draggables) consume clicks before the world: no click-through move/shoot/ClickThisObject under them, so no guard code needed. block_input additionally swallows clicks on non-interactable containers; a decorative classed element that must NOT eat world clicks sets click_through = true.
|
|
1952
2124
|
- Scrollable container: set clip_to_bounds=true + block_input=true on the container, then SetUiScrollOffset(X, Y) shifts its content (children move by -offset; rect/clip stay). Read back with GetUiScrollOffsetX/Y(). Wire event OnMouseScroll { } (Event.ScrollDelta, positive = wheel up) to adjust Y. Wheel over input-blocking UI does not zoom the camera. Authored start offset: scroll_offset = (x, y).
|
|
1953
|
-
- Rounded corners + border work on frame/button/progressBar (
|
|
1954
|
-
-
|
|
2125
|
+
- Rounded corners + border work on frame/button/progressBar (textLabel: neither; image: corner_radius only): corner_radius = N (base units, all 4 corners), border_width = N (base units; border shows only when > 0), border_color = #RRGGBB | #RRGGBBAA | (r,g,b). Authoring-only: no SetUi* simcall changes these at runtime.
|
|
2126
|
+
- progressBar: color = fill, background_color = track; fill ratio via SetProgress(Value, MaxValue).
|
|
2127
|
+
- count = N (2..=256, canvas child elements): parse-time repeat — the element and its whole subtree expand into N copies named <Name>0..<Name>N-1 (e.g. `frame Slot { count = 9 ... }` -> Slot0..Slot8, each resolvable by name). Use for marker/gauge/slot pools instead of hand-numbering; copies stack until code positions them via SetUiOffset; resolve copies dynamically via Panel.FindChildUI("Slot" + i). Counts nest (rows x cells).
|
|
1955
2128
|
- image nine_slice = (uv_frac, border): scalable panel art — the outer uv_frac (0..0.5) of the texture becomes a fixed border-unit rim (corners keep size, edges/center stretch). Use for stretchy frames/panels from one texture.
|
|
1956
2129
|
- frame/vStack/hStack/grid also accept image = "image.*" as a background fill (drawn over color/border, under children; nine_slice applies). Combine with material= freely.
|
|
1957
2130
|
- text/button shadows are off by default; set shadow_color = #000000B4 (or any alpha > 0) to enable.
|
|
@@ -1960,10 +2133,11 @@ event InventoryDropOnWorld
|
|
|
1960
2133
|
- Colors accept optional 8-digit hex alpha (e.g. #3A3A3AFF) in addition to #RRGGBB and (r,g,b).
|
|
1961
2134
|
- Two-stop gradients: gradient(colorA, colorB[, angle_deg]) (linear; angle 0 = top->bottom, 90 = left->right) or radial(inner, outer) (center->corners). ONLY on frame/vStack/hStack/grid `color` (fill) and on `border_color` of frame-like UI — NOT on button/textLabel colors (wrap the button in a gradient frame instead).
|
|
1962
2135
|
|
|
1963
|
-
-
|
|
2136
|
+
- Give visible UI a light theme pass by default: one Google font, one accent color on primary buttons/highlights, corner_radius on panels/buttons, panel colors from the game's palette (not pure black) — a few properties total.
|
|
2137
|
+
- Example (hugging vStack panel; themed: font + accent + rounded corners + subtle border):
|
|
1964
2138
|
vStack Panel { offset=(40,40) padding=12 gap=8 color=#1E1E1EF0 corner_radius=12 border_width=1 border_color=#3A3A3AFF
|
|
1965
|
-
textLabel Title { size=(200,28) text="Shop" }
|
|
1966
|
-
button Buy { size=(200,44) text="Buy" corner_radius=8 }
|
|
2139
|
+
textLabel Title { size=(200,28) text="Shop" font="font.google.Cinzel" font_size=22 }
|
|
2140
|
+
button Buy { size=(200,44) text="Buy" corner_radius=8 color=#7A4FBF }
|
|
1967
2141
|
}
|
|
1968
2142
|
- Example (gradient card fill + gradient border):
|
|
1969
2143
|
frame Card { size=(320,180) color=gradient(#1A2A6C, #B21F1F, 45) corner_radius=16 border_width=2 border_color=radial(#FFFFFF80, #FFFFFF10) }
|
|
@@ -2019,6 +2193,8 @@ event InventoryDropOnWorld {
|
|
|
2019
2193
|
- use the function PlayAnimationOnRepeat to loop animations like run forever
|
|
2020
2194
|
- PlayAnimationOnRepeat(Ori.anim.default.goblin.run)
|
|
2021
2195
|
- PlayAnimation(Ori.anim.default.goblin.attack)
|
|
2196
|
+
- a marketplace model's own clips: Ori.anim.<creator>.<model>.<clip> (e.g. Ori.anim.vk7t7mestuxl_5378.murlocfishgi.attack); clip names are exact-case
|
|
2197
|
+
- Timing a clip to a moment (hand/bat/racket meeting a ball): call PlayAnimationHittingContactIn(Anim, ContactPercent, SecondsUntilContact) every frame - it returns false and does nothing while still too early, then fires rate-scaled so ContactPercent lands exactly on contact, and refuses to restart a clip it already started (no 'started' guard var needed). GetAnimationLeadSeconds tells how early it can fire. Never derive a trigger delay from GetAnimationDuration by hand.
|
|
2022
2198
|
|
|
2023
2199
|
- Rigged store characters can also play canonical clips (world `#clip Name on canon` blocks -> Ori.anim.canon.Name) and other humanoid rigs' clips (e.g. a template mesh's Ori.anim.mesh.<Mesh>.walk) — the engine retargets by bone-name map, display-only
|
|
2024
2200
|
|
|
@@ -2026,8 +2202,8 @@ event InventoryDropOnWorld {
|
|
|
2026
2202
|
- the clip is picked automatically: .idle when still, .run when moving (falling back to .walk), .jumpHover when airborne; camera-locked players also use .runBack/.runLeft/.runRight by movement direction
|
|
2027
2203
|
- using PlayAnimation will stop these animations until the animation is finished
|
|
2028
2204
|
- DisableAutoMoveAnimations() to turn this off
|
|
2029
|
-
- Rigged humanoid store characters
|
|
2030
|
-
- Custom/procedural character: author ONE rigged #mesh (bone(...) joints + idle/walk #clips, standard bone names — see the humanoid example in the mesh skill) and SetModel it; auto move-animations then drive it. AttachTo assembly is for props/machines (turret, arm -> gun), not walking bodies
|
|
2205
|
+
- Rigged humanoid models (store characters or DSL rigs) that ship no `.idle` clip idle/walk/run (jumpHover airborne) with the built-in canonical clips automatically (display-only retarget); models with their own clips always use them
|
|
2206
|
+
- Custom/procedural character: author ONE rigged #mesh (bone(...) joints + optional idle/walk #clips, standard bone names — see the humanoid example in the mesh skill) and SetModel it; auto move-animations then drive it. AttachTo assembly is for props/machines (turret, arm -> gun), not walking bodies
|
|
2031
2207
|
- Procedural bone layer on any bone(...) rig (display-only, max 16 bones): SetBoneRotation(Mix) replaces/blends a joint over the playing clip, BoneLookAt aims one at a world point, PlayUpperBodyAnimation layers a clip over the base walk. Head-track + breathe over auto-walk:
|
|
2032
2208
|
forever {
|
|
2033
2209
|
BoneLookAt("Head", Friend.GetCenterPosition(), 55, 240)
|
|
@@ -2035,13 +2211,2547 @@ event InventoryDropOnWorld {
|
|
|
2035
2211
|
}
|
|
2036
2212
|
|
|
2037
2213
|
- Die() auto-spawns a visual-only corpse+animation; non-player deaths dissolve away during the die animation and despawn the object
|
|
2038
|
-
- Die() on a PLAYER (human or AI bot) is a warp-only death: corpse at the death spot, body warps to Origin(), class vars/cooldowns SURVIVE, OnSpawned does not re-run, the player is never despawned (attacks landing the same frame are dropped). Respawn pattern: in WasAttacked at HP <= 0 call Die() and start a respawn Timer; when it finishes, restore HP and TeleportTo(your team spawn). Removing a bot needs DespawnAIPlayer()/Destroy()
|
|
2214
|
+
- Die() on a PLAYER (human or AI bot) is a warp-only death: corpse at the death spot, body warps to Origin(), class vars/cooldowns SURVIVE, OnSpawned does not re-run, the player is never despawned (attacks landing the same frame are dropped). Respawn pattern: in WasAttacked at HP <= 0 call Die() and start a respawn Timer; when it finishes, restore HP and TeleportTo(your team spawn). Removing a bot needs DespawnAIPlayer()/Destroy(). No engine call re-runs OnSpawned or resets class vars - put per-life re-init in one func called from both OnSpawned and your respawn point
|
|
2039
2215
|
</animations>
|
|
2040
2216
|
|
|
2041
2217
|
|
|
2042
2218
|
<world_description_examples>
|
|
2043
2219
|
These examples are trimmed-down teaching games: copy their patterns, not their scope — a real request usually deserves more content and polish than any example here.
|
|
2044
2220
|
Examples by genre: stardew_valley (farming/life-sim), vampire_survivor (top-down action/swarm), breakout (2D arcade/side-view), dota (top-down hero/lane).
|
|
2221
|
+
<example_breakout>
|
|
2222
|
+
// Breakout (Fixed-Bounds Top-Down)
|
|
2223
|
+
// Multiplayer: shared board.
|
|
2224
|
+
// One shared brick field and one shared ball; each player gets one paddle in the same arena.
|
|
2225
|
+
// Do not duplicate the whole map unless intentionally making parallel solo boards.
|
|
2226
|
+
alias model.ball // bare: auto-binds a generated mesh from the library
|
|
2227
|
+
|
|
2228
|
+
#class BoardManager
|
|
2229
|
+
var TS = 50
|
|
2230
|
+
var W = 12
|
|
2231
|
+
var H = 16
|
|
2232
|
+
event OnSpawned {
|
|
2233
|
+
SetVisible(false)
|
|
2234
|
+
|
|
2235
|
+
// Spawn one shared ball for the whole match.
|
|
2236
|
+
var ball = SpawnObject(SavedObject.ball, Position(0, (-H*TS*0.5) + TS*2.6, 0))
|
|
2237
|
+
ball.BoundCenterX = 0
|
|
2238
|
+
ball.BoundHalfX = W*TS*0.5 - TS*0.3
|
|
2239
|
+
ball.BoundTopY = H*TS*0.5 - TS*0.4
|
|
2240
|
+
ball.BoundBottomY = -H*TS*0.5
|
|
2241
|
+
|
|
2242
|
+
// Build one shared brick field. Destroyed bricks disappear for everyone.
|
|
2243
|
+
for r in 0..4 {
|
|
2244
|
+
for c in 0..10 {
|
|
2245
|
+
var x = (c - 5) * TS * 0.9
|
|
2246
|
+
var y = (r * TS * 0.7) + TS * 3.5
|
|
2247
|
+
var b = SpawnObject(SavedObject.brick, Position(x, y, 0))
|
|
2248
|
+
b.SetColor(0.6, 0.3 + r*0.1, 0.9 - c*0.05)
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
#end
|
|
2253
|
+
|
|
2254
|
+
#class player
|
|
2255
|
+
var TS = 50
|
|
2256
|
+
var W = 12
|
|
2257
|
+
var H = 16
|
|
2258
|
+
var pad = 0.6
|
|
2259
|
+
var PaddleObj: Object
|
|
2260
|
+
event OnSpawned {
|
|
2261
|
+
SetPlayerModeSpectator()
|
|
2262
|
+
SetVisible(false)
|
|
2263
|
+
|
|
2264
|
+
var minX = (-W*TS*0.5 - pad*TS)
|
|
2265
|
+
var maxX = ( W*TS*0.5 + pad*TS)
|
|
2266
|
+
var minY = (-H*TS*0.5 - pad*TS)
|
|
2267
|
+
var maxY = ( H*TS*0.5 + pad*TS)
|
|
2268
|
+
SetCameraToTopDownSpectatorFixedBoundsXY(minX, maxX, minY, maxY)
|
|
2269
|
+
SetCameraCollisionEnabled(false)
|
|
2270
|
+
|
|
2271
|
+
// Each player adds a paddle to the same shared board.
|
|
2272
|
+
// Compact arcade layout: first few players get nearby bottom lanes.
|
|
2273
|
+
var lane = GetPlayerIndex()
|
|
2274
|
+
if lane > 5 { lane = 5 }
|
|
2275
|
+
var laneY = (-H*TS*0.5) + TS*(1.0 + lane*0.8)
|
|
2276
|
+
PaddleObj = SpawnObject(SavedObject.paddle, Position(0, laneY, 0))
|
|
2277
|
+
PaddleObj.SetPlayerOwnerIndex(GetPlayerIndex())
|
|
2278
|
+
PaddleObj.SetColor(0.2 + lane*0.12, 0.8 - lane*0.08, 1.0)
|
|
2279
|
+
|
|
2280
|
+
var PaddleHalf = (W*TS*0.5 - TS*0.6)
|
|
2281
|
+
forever {
|
|
2282
|
+
var move = GetMoveVectorWorld()
|
|
2283
|
+
var vx = move.X * 600
|
|
2284
|
+
var p = PaddleObj.GetPosition()
|
|
2285
|
+
var nextX = p.X + vx*DeltaSecs()
|
|
2286
|
+
if (vx < 0 and nextX <= -PaddleHalf) or (vx > 0 and nextX >= PaddleHalf) {
|
|
2287
|
+
vx = 0
|
|
2288
|
+
}
|
|
2289
|
+
PaddleObj.MoveWithVelocityContinuously(Vector(vx, 0, 0))
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
event OnDestroy {
|
|
2293
|
+
if PaddleObj.Exists() { PaddleObj.Destroy() }
|
|
2294
|
+
}
|
|
2295
|
+
#end
|
|
2296
|
+
|
|
2297
|
+
#class Ball
|
|
2298
|
+
var V = Vector(260, 260, 0)
|
|
2299
|
+
var ServePos = Position(0, 0, 0)
|
|
2300
|
+
var BoundCenterX = 0
|
|
2301
|
+
var BoundHalfX = 0
|
|
2302
|
+
var BoundTopY = 0
|
|
2303
|
+
var BoundBottomY = 0
|
|
2304
|
+
event OnSpawned {
|
|
2305
|
+
SetSizeTiles(0.5, 0.5, 0.5)
|
|
2306
|
+
SnapToGroundWithTag(Tag.Ground)
|
|
2307
|
+
ServePos = GetPosition()
|
|
2308
|
+
}
|
|
2309
|
+
event Update {
|
|
2310
|
+
var n = GetPosition() + V * DeltaSecs()
|
|
2311
|
+
|
|
2312
|
+
// Reflect on bounds using predicted next pos.
|
|
2313
|
+
if n.X <= BoundCenterX - BoundHalfX or n.X >= BoundCenterX + BoundHalfX { V.X = -V.X }
|
|
2314
|
+
if n.Y >= BoundTopY { V.Y = -V.Y }
|
|
2315
|
+
// Missed ball: soft serve reset (RestartGame() would reload the whole project).
|
|
2316
|
+
if n.Y <= BoundBottomY {
|
|
2317
|
+
SetPosition(ServePos)
|
|
2318
|
+
V = Vector(260, 260, 0)
|
|
2319
|
+
return
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
for o in GetTouchedObjects() {
|
|
2323
|
+
if o.HasTag(Tag.Brick) {
|
|
2324
|
+
o.Destroy()
|
|
2325
|
+
V.Y = -V.Y
|
|
2326
|
+
break
|
|
2327
|
+
}
|
|
2328
|
+
if o.HasTag(Tag.Paddle) {
|
|
2329
|
+
V.Y = V.Y.Abs()
|
|
2330
|
+
V.X = V.X + (GetPosition().X - o.GetPosition().X) * 4
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
MoveWithVelocityContinuously(V)
|
|
2335
|
+
}
|
|
2336
|
+
#end
|
|
2337
|
+
|
|
2338
|
+
saved paddle { object P { model = "alias.model.paddle" size_tiles = (2, 0.6, 0.2) tag = Tag.Paddle } }
|
|
2339
|
+
saved ball { object B : Ball { model = "alias.model.ball" } }
|
|
2340
|
+
saved brick { object K { model = "alias.model.brick" size_tiles = (0.8, 0.5, 0.3) tag = Tag.Brick } }
|
|
2341
|
+
|
|
2342
|
+
material GroundMat { base = "PolishedConcrete" }
|
|
2343
|
+
object ground { model = "cube" material = "GroundMat" position = (0, 0, -100) size = (10000, 10000, 100) tag = Tag.Ground }
|
|
2344
|
+
display manager : BoardManager { visible = false }
|
|
2345
|
+
screenCanvas HUD { textLabel T { offset = (20, 20) size = (520, 28) align = (left, center) text = "Shared board: move your paddle with A/D or mobile thumbstick" color = (1, 1, 1) } }
|
|
2346
|
+
|
|
2347
|
+
</example_breakout>
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
<example_dota>
|
|
2351
|
+
// dota.weave
|
|
2352
|
+
// Multiplayer: team-based MOBA (per-player heroes via SetPlayerOwnerIndex/SetTeam, ByThisPlayer input).
|
|
2353
|
+
// Caveat: the shop window is a single shared canvas.
|
|
2354
|
+
|
|
2355
|
+
alias effect.explosion = "ori.effect.nuttametee_b_2523.w_explosion0"
|
|
2356
|
+
alias model.player_unit = "sphere"
|
|
2357
|
+
alias model.creep = "sphere"
|
|
2358
|
+
alias model.projectile // bare: auto-binds a generated mesh from the library
|
|
2359
|
+
|
|
2360
|
+
#class TowerBrain
|
|
2361
|
+
var HP = 500
|
|
2362
|
+
var MaxHP = 500
|
|
2363
|
+
var LastShotTime: Number
|
|
2364
|
+
var AttackRange = 270
|
|
2365
|
+
event OnSpawned {
|
|
2366
|
+
LastShotTime = TimeSecs() - 1000.0
|
|
2367
|
+
SetHoverHPBar(HP, MaxHP)
|
|
2368
|
+
}
|
|
2369
|
+
event Update {
|
|
2370
|
+
var team = GetTeam()
|
|
2371
|
+
var bestDist = 1000000
|
|
2372
|
+
var target = None
|
|
2373
|
+
|
|
2374
|
+
for creep in GetAllObjectsWithSavedType(SavedObject.CreepUnit) {
|
|
2375
|
+
if creep.GetTeam() != team {
|
|
2376
|
+
var dist = DistanceXYTo(creep)
|
|
2377
|
+
if dist < bestDist and dist <= AttackRange {
|
|
2378
|
+
bestDist = dist
|
|
2379
|
+
target = creep
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
if target == None {
|
|
2384
|
+
for pUnit in GetAllObjectsWithSavedType(SavedObject.PlayerUnit) {
|
|
2385
|
+
if pUnit.GetTeam() != team {
|
|
2386
|
+
var dist = DistanceXYTo(pUnit)
|
|
2387
|
+
if dist < bestDist and dist <= AttackRange {
|
|
2388
|
+
bestDist = dist
|
|
2389
|
+
target = pUnit
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
if target == None { return }
|
|
2395
|
+
var now = TimeSecs()
|
|
2396
|
+
if (now - LastShotTime) < 1.5 { return }
|
|
2397
|
+
var start = GetPosition().ShiftUp(50)
|
|
2398
|
+
var proj = SpawnObject(SavedObject.Projectile, start)
|
|
2399
|
+
proj.target = target
|
|
2400
|
+
proj.SetColor(0.40, 0.700, 0.9)
|
|
2401
|
+
LastShotTime = now
|
|
2402
|
+
}
|
|
2403
|
+
event WasAttacked {
|
|
2404
|
+
var dmg = Event.Damage.Round()
|
|
2405
|
+
HP = HP - dmg
|
|
2406
|
+
if HP < 0 { HP = 0 }
|
|
2407
|
+
SetHoverHPBar(HP, MaxHP)
|
|
2408
|
+
SpawnFloatingText("-" + dmg, Vector(1, 0, 0))
|
|
2409
|
+
if HP <= 0 {
|
|
2410
|
+
SpawnEffect(Ori.alias.effect.explosion, GetPosition())
|
|
2411
|
+
Die()
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
#end
|
|
2415
|
+
|
|
2416
|
+
|
|
2417
|
+
#class GameManager
|
|
2418
|
+
event OnSpawned {
|
|
2419
|
+
var MapHalfTiles = 12
|
|
2420
|
+
var WallThicknessTiles = 1
|
|
2421
|
+
var WallHeightTiles = 2
|
|
2422
|
+
var WallLenTiles = (MapHalfTiles * 2 + 2)
|
|
2423
|
+
|
|
2424
|
+
var wTop = SpawnAtTile(SavedObject.WallCube, 0, MapHalfTiles + (WallThicknessTiles * 0.5))
|
|
2425
|
+
wTop.SetSizeTiles(WallLenTiles, WallThicknessTiles, WallHeightTiles)
|
|
2426
|
+
var wBottom = SpawnAtTile(SavedObject.WallCube, 0, -MapHalfTiles - (WallThicknessTiles * 0.5))
|
|
2427
|
+
wBottom.SetSizeTiles(WallLenTiles, WallThicknessTiles, WallHeightTiles)
|
|
2428
|
+
var wLeft = SpawnAtTile(SavedObject.WallCube, -MapHalfTiles - (WallThicknessTiles * 0.5), 0)
|
|
2429
|
+
wLeft.SetSizeTiles(WallThicknessTiles, WallLenTiles, WallHeightTiles)
|
|
2430
|
+
var wRight = SpawnAtTile(SavedObject.WallCube, MapHalfTiles + (WallThicknessTiles * 0.5), 0)
|
|
2431
|
+
wRight.SetSizeTiles(WallThicknessTiles, WallLenTiles, WallHeightTiles)
|
|
2432
|
+
|
|
2433
|
+
var marginTiles = 2
|
|
2434
|
+
var CP1TilePos = Vector(-MapHalfTiles + marginTiles, -MapHalfTiles + marginTiles, 0)
|
|
2435
|
+
var CP2TilePos = Vector(-MapHalfTiles + marginTiles, MapHalfTiles - marginTiles, 0)
|
|
2436
|
+
var CP3TilePos = Vector( MapHalfTiles - marginTiles, -MapHalfTiles + marginTiles, 0)
|
|
2437
|
+
var CP4TilePos = Vector( MapHalfTiles - marginTiles, MapHalfTiles - marginTiles, 0)
|
|
2438
|
+
var CP5TilePos = Vector(0, 0, 0)
|
|
2439
|
+
|
|
2440
|
+
var CP1 = SpawnAtTile(SavedObject.Checkpoint, CP1TilePos); CP1.AddTag(Tag.CP1)
|
|
2441
|
+
var CP2o = SpawnAtTile(SavedObject.Checkpoint, CP2TilePos); CP2o.AddTag(Tag.CP2)
|
|
2442
|
+
var CP3o = SpawnAtTile(SavedObject.Checkpoint, CP3TilePos); CP3o.AddTag(Tag.CP3)
|
|
2443
|
+
var CP4 = SpawnAtTile(SavedObject.Checkpoint, CP4TilePos); CP4.AddTag(Tag.CP4)
|
|
2444
|
+
var CP5o = SpawnAtTile(SavedObject.Checkpoint, CP5TilePos); CP5o.AddTag(Tag.CP5)
|
|
2445
|
+
|
|
2446
|
+
var laneThicknessTiles = 3.0
|
|
2447
|
+
var laneHeightTiles = 0.1
|
|
2448
|
+
|
|
2449
|
+
var mid12 = (CP1TilePos + CP2TilePos) * 0.5
|
|
2450
|
+
var d12 = CP1TilePos.DistanceTo(CP2TilePos)
|
|
2451
|
+
var L12 = SpawnAtTile(SavedObject.LaneCube, mid12)
|
|
2452
|
+
L12.SetSizeTiles(d12, laneThicknessTiles, laneHeightTiles)
|
|
2453
|
+
L12.LookAtTileHorizontal(CP2TilePos)
|
|
2454
|
+
var mid15 = (CP1TilePos + CP5TilePos) * 0.5
|
|
2455
|
+
var d15 = CP1TilePos.DistanceTo(CP5TilePos)
|
|
2456
|
+
var L15 = SpawnAtTile(SavedObject.LaneCube, mid15)
|
|
2457
|
+
L15.SetSizeTiles(d15, laneThicknessTiles, laneHeightTiles)
|
|
2458
|
+
L15.LookAtTileHorizontal(CP5TilePos)
|
|
2459
|
+
var mid13 = (CP1TilePos + CP3TilePos) * 0.5
|
|
2460
|
+
var d13 = CP1TilePos.DistanceTo(CP3TilePos)
|
|
2461
|
+
var L13 = SpawnAtTile(SavedObject.LaneCube, mid13)
|
|
2462
|
+
L13.SetSizeTiles(d13, laneThicknessTiles, laneHeightTiles)
|
|
2463
|
+
L13.LookAtTileHorizontal(CP3TilePos)
|
|
2464
|
+
var mid42 = (CP4TilePos + CP2TilePos) * 0.5
|
|
2465
|
+
var d42 = CP4TilePos.DistanceTo(CP2TilePos)
|
|
2466
|
+
var L42 = SpawnAtTile(SavedObject.LaneCube, mid42)
|
|
2467
|
+
L42.SetSizeTiles(d42, laneThicknessTiles, laneHeightTiles)
|
|
2468
|
+
L42.LookAtTileHorizontal(CP2TilePos)
|
|
2469
|
+
var mid45 = (CP4TilePos + CP5TilePos) * 0.5
|
|
2470
|
+
var d45 = CP4TilePos.DistanceTo(CP5TilePos)
|
|
2471
|
+
var L45 = SpawnAtTile(SavedObject.LaneCube, mid45)
|
|
2472
|
+
L45.SetSizeTiles(d45, laneThicknessTiles, laneHeightTiles)
|
|
2473
|
+
L45.LookAtTileHorizontal(CP5TilePos)
|
|
2474
|
+
var mid43 = (CP4TilePos + CP3TilePos) * 0.5
|
|
2475
|
+
var d43 = CP4TilePos.DistanceTo(CP3TilePos)
|
|
2476
|
+
var L43 = SpawnAtTile(SavedObject.LaneCube, mid43)
|
|
2477
|
+
L43.SetSizeTiles(d43, laneThicknessTiles, laneHeightTiles)
|
|
2478
|
+
L43.LookAtTileHorizontal(CP3TilePos)
|
|
2479
|
+
|
|
2480
|
+
var Dir_CP2_to_CP1 = (CP1TilePos - CP2TilePos).GetNormal()
|
|
2481
|
+
var Pos_Tower_A = CP2TilePos + (Dir_CP2_to_CP1 * 6)
|
|
2482
|
+
var TowerA = SpawnAtTile(SavedObject.TowerUnitR350, Pos_Tower_A)
|
|
2483
|
+
TowerA.SetColor(0, 1, 0); TowerA.SetTeam(0)
|
|
2484
|
+
var Dir_CP5_to_CP1 = (CP1TilePos - CP5TilePos).GetNormal()
|
|
2485
|
+
var Pos_Tower_B = CP5TilePos + (Dir_CP5_to_CP1 * 5)
|
|
2486
|
+
var TowerB = SpawnAtTile(SavedObject.TowerUnit, Pos_Tower_B)
|
|
2487
|
+
TowerB.SetColor(0, 1, 0); TowerB.SetTeam(0)
|
|
2488
|
+
var Dir_CP3_to_CP1 = (CP1TilePos - CP3TilePos).GetNormal()
|
|
2489
|
+
var Pos_Tower_C = CP3TilePos + (Dir_CP3_to_CP1 * 6)
|
|
2490
|
+
var TowerC = SpawnAtTile(SavedObject.TowerUnitR350, Pos_Tower_C)
|
|
2491
|
+
TowerC.SetColor(0, 1, 0); TowerC.SetTeam(0)
|
|
2492
|
+
var Dir_CP2_to_CP4 = (CP4TilePos - CP2TilePos).GetNormal()
|
|
2493
|
+
var Pos_Tower_X = CP2TilePos + (Dir_CP2_to_CP4 * 6)
|
|
2494
|
+
var TowerX = SpawnAtTile(SavedObject.TowerUnitR350, Pos_Tower_X)
|
|
2495
|
+
TowerX.SetColor(1, 0, 0); TowerX.SetTeam(1)
|
|
2496
|
+
var Dir_CP5_to_CP4 = (CP4TilePos - CP5TilePos).GetNormal()
|
|
2497
|
+
var Pos_Tower_Y = CP5TilePos + (Dir_CP5_to_CP4 * 5)
|
|
2498
|
+
var TowerY = SpawnAtTile(SavedObject.TowerUnit, Pos_Tower_Y)
|
|
2499
|
+
TowerY.SetColor(1, 0, 0); TowerY.SetTeam(1)
|
|
2500
|
+
var Dir_CP3_to_CP4 = (CP4TilePos - CP3TilePos).GetNormal()
|
|
2501
|
+
var Pos_Tower_Z = CP3TilePos + (Dir_CP3_to_CP4 * 6)
|
|
2502
|
+
var TowerZ = SpawnAtTile(SavedObject.TowerUnitR350, Pos_Tower_Z)
|
|
2503
|
+
TowerZ.SetColor(1, 0, 0); TowerZ.SetTeam(1)
|
|
2504
|
+
// Player heroes spawn per join in '#class player' OnSpawned (any player count works).
|
|
2505
|
+
forever {
|
|
2506
|
+
var cg12a = SpawnAtTile(SavedObject.CreepUnit, CP1TilePos + Vector(0, 1, 0))
|
|
2507
|
+
cg12a.SetColor(0, 1, 0); cg12a.SetTeam(0); cg12a.AddTag(Tag.PathToCP2)
|
|
2508
|
+
var cg12b = SpawnAtTile(SavedObject.CreepUnit, CP1TilePos + Vector(0, -1, 0))
|
|
2509
|
+
cg12b.SetColor(0, 1, 0); cg12b.SetTeam(0); cg12b.AddTag(Tag.PathToCP2)
|
|
2510
|
+
var cg15a = SpawnAtTile(SavedObject.CreepUnit, CP1TilePos + Vector(1, 0, 0))
|
|
2511
|
+
cg15a.SetColor(0, 1, 0); cg15a.SetTeam(0); cg15a.AddTag(Tag.PathToCP5)
|
|
2512
|
+
var cg15b = SpawnAtTile(SavedObject.CreepUnit, CP1TilePos + Vector(-1, 0, 0))
|
|
2513
|
+
cg15b.SetColor(0, 1, 0); cg15b.SetTeam(0); cg15b.AddTag(Tag.PathToCP5)
|
|
2514
|
+
var cg13a = SpawnAtTile(SavedObject.CreepUnit, CP1TilePos + Vector(0, 2, 0))
|
|
2515
|
+
cg13a.SetColor(0, 1, 0); cg13a.SetTeam(0); cg13a.AddTag(Tag.PathToCP3)
|
|
2516
|
+
var cg13b = SpawnAtTile(SavedObject.CreepUnit, CP1TilePos + Vector(0, -2, 0))
|
|
2517
|
+
cg13b.SetColor(0, 1, 0); cg13b.SetTeam(0); cg13b.AddTag(Tag.PathToCP3)
|
|
2518
|
+
var cr42a = SpawnAtTile(SavedObject.CreepUnit, CP4TilePos + Vector(0, 1, 0))
|
|
2519
|
+
cr42a.SetColor(1, 0, 0); cr42a.SetTeam(1); cr42a.AddTag(Tag.PathToCP2)
|
|
2520
|
+
var cr42b = SpawnAtTile(SavedObject.CreepUnit, CP4TilePos + Vector(0, -1, 0))
|
|
2521
|
+
cr42b.SetColor(1, 0, 0); cr42b.SetTeam(1); cr42b.AddTag(Tag.PathToCP2)
|
|
2522
|
+
var cr45a = SpawnAtTile(SavedObject.CreepUnit, CP4TilePos + Vector(1, 0, 0))
|
|
2523
|
+
cr45a.SetColor(1, 0, 0); cr45a.SetTeam(1); cr45a.AddTag(Tag.PathToCP5)
|
|
2524
|
+
var cr45b = SpawnAtTile(SavedObject.CreepUnit, CP4TilePos + Vector(-1, 0, 0))
|
|
2525
|
+
cr45b.SetColor(1, 0, 0); cr45b.SetTeam(1); cr45b.AddTag(Tag.PathToCP5)
|
|
2526
|
+
var cr43a = SpawnAtTile(SavedObject.CreepUnit, CP4TilePos + Vector(0, 2, 0))
|
|
2527
|
+
cr43a.SetColor(1, 0, 0); cr43a.SetTeam(1); cr43a.AddTag(Tag.PathToCP3)
|
|
2528
|
+
var cr43b = SpawnAtTile(SavedObject.CreepUnit, CP4TilePos + Vector(0, -2, 0))
|
|
2529
|
+
cr43b.SetColor(1, 0, 0); cr43b.SetTeam(1); cr43b.AddTag(Tag.PathToCP3)
|
|
2530
|
+
Wait(5)
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
#end
|
|
2534
|
+
#class UnitStats
|
|
2535
|
+
var HP = 100
|
|
2536
|
+
var MaxHP = 100
|
|
2537
|
+
var MP = 100
|
|
2538
|
+
var Level = 1
|
|
2539
|
+
var XP = 0
|
|
2540
|
+
var XPToNextLevel = 100
|
|
2541
|
+
var Armor = 3
|
|
2542
|
+
var LastShotTime: Number
|
|
2543
|
+
var MoveGoal: Position
|
|
2544
|
+
var HasMoveGoal = false
|
|
2545
|
+
|
|
2546
|
+
event OnSpawned {
|
|
2547
|
+
LastShotTime = TimeSecs() - 1000.0
|
|
2548
|
+
SetHoverHPBar(HP, 100)
|
|
2549
|
+
SetHoverBar(1, MP, 100, Vector(0.2, 0.6, 1.0), Vector(0.1, 0.1, 0.2), 0, 8, 50, 6)
|
|
2550
|
+
}
|
|
2551
|
+
func LevelUp() {
|
|
2552
|
+
Level += 1
|
|
2553
|
+
XP = XP - XPToNextLevel
|
|
2554
|
+
XPToNextLevel = (XPToNextLevel * 1.1).Round()
|
|
2555
|
+
Armor = 3 + ((Level - 1) * 1.7)
|
|
2556
|
+
|
|
2557
|
+
var maxHP = 100 + (Level * 10)
|
|
2558
|
+
var maxMP = 100 + (Level * 5)
|
|
2559
|
+
HP = maxHP
|
|
2560
|
+
MP = maxMP
|
|
2561
|
+
SetHoverHPBar(HP, maxHP)
|
|
2562
|
+
SetHoverBar(1, MP, maxMP, Vector(0.2, 0.6, 1.0), Vector(0.1, 0.1, 0.2), 0, 8, 50, 6)
|
|
2563
|
+
SpawnFloatingText("LEVEL UP!", Vector(1, 0.8, 0))
|
|
2564
|
+
}
|
|
2565
|
+
func GainXP(amount: Number) {
|
|
2566
|
+
XP += amount
|
|
2567
|
+
SpawnFloatingText("+" + amount + " XP", Vector(1, 1, 0))
|
|
2568
|
+
while XP >= XPToNextLevel {
|
|
2569
|
+
LevelUp()
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
event Update {
|
|
2573
|
+
if HasMoveGoal {
|
|
2574
|
+
if DistanceXYTo(MoveGoal) < GetSizeX() * 0.5 { HasMoveGoal = false }
|
|
2575
|
+
else { MoveTowardContinuousHorizontalFacing(MoveGoal, 250) }
|
|
2576
|
+
}
|
|
2577
|
+
var team = GetTeam()
|
|
2578
|
+
var best = 1000000
|
|
2579
|
+
var target = None
|
|
2580
|
+
for e in GetAllObjectsWithSavedType(SavedObject.PlayerUnit) {
|
|
2581
|
+
if e.GetTeam() != team {
|
|
2582
|
+
var d = DistanceXYTo(e)
|
|
2583
|
+
if d < best and d <= 200 { best = d; target = e }
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
for c in GetAllObjectsWithSavedType(SavedObject.CreepUnit) {
|
|
2587
|
+
if c.GetTeam() != team {
|
|
2588
|
+
var d2 = DistanceXYTo(c)
|
|
2589
|
+
if d2 < best and d2 <= 200 { best = d2; target = c }
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
for t in GetAllObjectsWithSavedType(SavedObject.TowerUnit) {
|
|
2593
|
+
if t.GetTeam() != team {
|
|
2594
|
+
var d3 = DistanceXYTo(t)
|
|
2595
|
+
if d3 < best and d3 <= 200 { best = d3; target = t }
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
if target == None { return }
|
|
2599
|
+
var now2 = TimeSecs()
|
|
2600
|
+
if (now2 - LastShotTime) < 0.8 { return }
|
|
2601
|
+
var start = GetPosition().ShiftUp(20)
|
|
2602
|
+
var proj = SpawnObject(SavedObject.Projectile, start)
|
|
2603
|
+
proj.target = target
|
|
2604
|
+
proj.SetColor(0.10, 0.600, 0)
|
|
2605
|
+
LastShotTime = now2
|
|
2606
|
+
}
|
|
2607
|
+
event WasAttacked {
|
|
2608
|
+
var dmg = Event.Damage.Round()
|
|
2609
|
+
HP = HP - dmg
|
|
2610
|
+
if HP < 0 { HP = 0 }
|
|
2611
|
+
SetHoverHPBar(HP, 100 + (Level * 10))
|
|
2612
|
+
SpawnFloatingText("-" + dmg, Vector(1, 0, 0))
|
|
2613
|
+
if HP <= 0 {
|
|
2614
|
+
SpawnEffect(Ori.alias.effect.explosion, GetPosition())
|
|
2615
|
+
Die()
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
#end
|
|
2619
|
+
#class ProjectileBrain
|
|
2620
|
+
var target: Object
|
|
2621
|
+
event OnSpawned {
|
|
2622
|
+
var col = GetColor()
|
|
2623
|
+
var dmg = (col.X * 100).Round()
|
|
2624
|
+
var spd = (col.Y * 1000).Round()
|
|
2625
|
+
if dmg <= 0 { dmg = 10 }
|
|
2626
|
+
if spd <= 0 { spd = 600 }
|
|
2627
|
+
|
|
2628
|
+
forever {
|
|
2629
|
+
if not target.Exists() { Destroy(); return }
|
|
2630
|
+
var aim = target.GetPosition()
|
|
2631
|
+
MoveTowardContinuousFacing(aim, spd)
|
|
2632
|
+
if DistanceTo(aim) <= 18 {
|
|
2633
|
+
Self.Attack(target, dmg)
|
|
2634
|
+
SpawnEffect(Ori.alias.effect.explosion, aim)
|
|
2635
|
+
Destroy()
|
|
2636
|
+
return
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
#end
|
|
2641
|
+
#class CreepBrain
|
|
2642
|
+
var HP = 10
|
|
2643
|
+
var LastAttackTime = 0
|
|
2644
|
+
var XPReward = 10
|
|
2645
|
+
event OnSpawned {
|
|
2646
|
+
SetHoverHPBar(HP, 10)
|
|
2647
|
+
}
|
|
2648
|
+
event Update {
|
|
2649
|
+
var dest = GetPosition()
|
|
2650
|
+
if HasTag(Tag.PathToCP2) {
|
|
2651
|
+
var rs = GetAllRegionsWithTag(Tag.CP2); if not rs.IsEmpty() { dest = rs.Get(0).GetPosition() }
|
|
2652
|
+
} else if HasTag(Tag.PathToCP5) {
|
|
2653
|
+
var rs2 = GetAllRegionsWithTag(Tag.CP5); if not rs2.IsEmpty() { dest = rs2.Get(0).GetPosition() }
|
|
2654
|
+
} else if HasTag(Tag.PathToCP3) {
|
|
2655
|
+
var rs3 = GetAllRegionsWithTag(Tag.CP3); if not rs3.IsEmpty() { dest = rs3.Get(0).GetPosition() }
|
|
2656
|
+
}
|
|
2657
|
+
if DistanceXYTo(dest) >= GetSizeX() * 0.5 {
|
|
2658
|
+
MoveTowardContinuousHorizontalFacing(dest, 180)
|
|
2659
|
+
}
|
|
2660
|
+
var now = TimeSecs()
|
|
2661
|
+
if (now - LastAttackTime) >= 1.0 {
|
|
2662
|
+
var team = GetTeam()
|
|
2663
|
+
var enemyTeam = 1 - team
|
|
2664
|
+
var target = None
|
|
2665
|
+
var best = 1000000
|
|
2666
|
+
for o in GetObjectsWithinDistanceWithTeam(30, enemyTeam) {
|
|
2667
|
+
if o.IsSavedType(SavedObject.CreepUnit) or o.IsSavedType(SavedObject.PlayerUnit) or o.IsSavedType(SavedObject.TowerUnit) {
|
|
2668
|
+
var dd = DistanceXYTo(o)
|
|
2669
|
+
if dd < best { best = dd; target = o }
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
if target != None {
|
|
2673
|
+
var dmg = RandomNumber(1, 3).Round()
|
|
2674
|
+
Self.Attack(target, dmg)
|
|
2675
|
+
LastAttackTime = now
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
event WasAttacked {
|
|
2680
|
+
var dmg = Event.Damage.Round()
|
|
2681
|
+
HP = HP - dmg
|
|
2682
|
+
if HP < 0 { HP = 0 }
|
|
2683
|
+
SetHoverHPBar(HP, 10)
|
|
2684
|
+
|
|
2685
|
+
SpawnFloatingText("-" + dmg, Vector(1, 0.5, 0))
|
|
2686
|
+
if HP <= 0 {
|
|
2687
|
+
for unit in GetAllObjectsWithSavedType(SavedObject.PlayerUnit) {
|
|
2688
|
+
unit.GainXP(XPReward)
|
|
2689
|
+
}
|
|
2690
|
+
SpawnEffect(Ori.alias.effect.explosion, GetPosition())
|
|
2691
|
+
Die()
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
#end
|
|
2695
|
+
|
|
2696
|
+
|
|
2697
|
+
#class player
|
|
2698
|
+
var IsAimingSkill = false
|
|
2699
|
+
var LastSkillQTime: Number
|
|
2700
|
+
var IsShowingCooldownIndicator = false
|
|
2701
|
+
var Gold = 200
|
|
2702
|
+
var GoldTimer: Timer // auto-constructed timer
|
|
2703
|
+
event OnSpawned {
|
|
2704
|
+
SetCameraToTopDown()
|
|
2705
|
+
SetVisible(false)
|
|
2706
|
+
SetPlayerModeSpectator()
|
|
2707
|
+
LastSkillQTime = TimeSecs() - 1000.0
|
|
2708
|
+
GoldTimer.StartCountDown(1)
|
|
2709
|
+
|
|
2710
|
+
var side = GetPlayerIndex() % 2
|
|
2711
|
+
SetTeam(side)
|
|
2712
|
+
|
|
2713
|
+
// Field my hero at my team's corner; slot offset keeps teammates from stacking.
|
|
2714
|
+
var slot = (GetPlayerIndex() / 2).RoundDown()
|
|
2715
|
+
var unitTile = Vector(-10 - slot, -10 + slot, 0)
|
|
2716
|
+
var unitColor = Vector(0, 1, 0)
|
|
2717
|
+
if side == 1 { unitTile = Vector(10 + slot, 10 - slot, 0); unitColor = Vector(1, 0, 0) }
|
|
2718
|
+
var u = SpawnAtTile(SavedObject.PlayerUnit, unitTile)
|
|
2719
|
+
u.SetColor(unitColor)
|
|
2720
|
+
u.SetTeam(side)
|
|
2721
|
+
u.SetPlayerOwnerIndex(GetPlayerIndex())
|
|
2722
|
+
u.SetHoverText(GetPlayerName()) // owner's username above the hero (the spectator body is hidden)
|
|
2723
|
+
}
|
|
2724
|
+
event OnDestroy {
|
|
2725
|
+
var u = GetMyUnit()
|
|
2726
|
+
if u.Exists() { u.Destroy() }
|
|
2727
|
+
}
|
|
2728
|
+
event KeyboardDownByThisPlayer[Q] {
|
|
2729
|
+
var cdLeft = 2.5 - (TimeSecs() - LastSkillQTime)
|
|
2730
|
+
if cdLeft <= 0 {
|
|
2731
|
+
IsAimingSkill = not IsAimingSkill
|
|
2732
|
+
IsShowingCooldownIndicator = false
|
|
2733
|
+
} else {
|
|
2734
|
+
IsShowingCooldownIndicator = not IsShowingCooldownIndicator
|
|
2735
|
+
IsAimingSkill = false
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
func GetMyUnit() -> UnitStats {
|
|
2739
|
+
var myIndex = GetPlayerIndex()
|
|
2740
|
+
for unit in GetAllObjectsWithSavedType(SavedObject.PlayerUnit) {
|
|
2741
|
+
if unit.GetPlayerOwnerIndex() == myIndex { return (unit as UnitStats) }
|
|
2742
|
+
}
|
|
2743
|
+
return None
|
|
2744
|
+
}
|
|
2745
|
+
event Update {
|
|
2746
|
+
var myUnit = GetMyUnit()
|
|
2747
|
+
if myUnit == None { return }
|
|
2748
|
+
SetCameraPivotTargetThisFrame(myUnit.GetPosition())
|
|
2749
|
+
if GoldTimer.IsDone() {
|
|
2750
|
+
Gold += 1
|
|
2751
|
+
GoldTimer.StartCountDown(1)
|
|
2752
|
+
}
|
|
2753
|
+
if IsShowingCooldownIndicator {
|
|
2754
|
+
var p = GetMouseTargetPositionOnPlaneZ(0)
|
|
2755
|
+
DrawSkillAreaCircle(p, 200, Vector(1, 0, 0))
|
|
2756
|
+
} else if IsAimingSkill {
|
|
2757
|
+
var p = GetMouseTargetPositionOnPlaneZ(0)
|
|
2758
|
+
DrawSkillAreaCircle(p, 200, Vector(1, 0.5, 0.2))
|
|
2759
|
+
}
|
|
2760
|
+
var cdLeft = 2.5 - (TimeSecs() - LastSkillQTime)
|
|
2761
|
+
var unitName = ""
|
|
2762
|
+
var myIndex = GetPlayerIndex()
|
|
2763
|
+
if myIndex == 0 { unitName = "A" }
|
|
2764
|
+
if myIndex == 1 { unitName = "B" }
|
|
2765
|
+
if myIndex == 2 { unitName = "C" }
|
|
2766
|
+
if myIndex == 3 { unitName = "X" }
|
|
2767
|
+
if myIndex == 4 { unitName = "Y" }
|
|
2768
|
+
if myIndex >= 5 { unitName = "Z" }
|
|
2769
|
+
|
|
2770
|
+
var statsLine1 = unitName + " | LV: " + myUnit.Level + " | ARM: " + myUnit.Armor + " | Gold: " + Gold
|
|
2771
|
+
var statsLine2 = "XP: " + myUnit.XP + "/" + myUnit.XPToNextLevel
|
|
2772
|
+
|
|
2773
|
+
var fullText = statsLine1 + "\n" + statsLine2
|
|
2774
|
+
if cdLeft > 0 {
|
|
2775
|
+
fullText = fullText + "\nSkill Cooldown"
|
|
2776
|
+
}
|
|
2777
|
+
myUnit.Say(fullText)
|
|
2778
|
+
}
|
|
2779
|
+
event ActionDownByThisPlayer[Primary] {
|
|
2780
|
+
var myUnit = GetMyUnit()
|
|
2781
|
+
if myUnit == None { return }
|
|
2782
|
+
var cdLeft = 2.5 - (TimeSecs() - LastSkillQTime)
|
|
2783
|
+
if IsAimingSkill and cdLeft > 0 {
|
|
2784
|
+
myUnit.SpawnFloatingText("COOLDOWN", Vector(1, 1, 0))
|
|
2785
|
+
return
|
|
2786
|
+
}
|
|
2787
|
+
if not IsAimingSkill { return }
|
|
2788
|
+
var manaCost = 10
|
|
2789
|
+
if myUnit.MP < manaCost {
|
|
2790
|
+
myUnit.SpawnFloatingText("Not enough mana", Vector(0.2, 0.6, 1.0))
|
|
2791
|
+
IsAimingSkill = false
|
|
2792
|
+
return
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
myUnit.MP -= manaCost
|
|
2796
|
+
myUnit.SetHoverBar(1, myUnit.MP, 100 + (myUnit.Level * 5), Vector(0.2, 0.6, 1.0), Vector(0.1, 0.1, 0.2), 0, 8, 50, 6)
|
|
2797
|
+
LastSkillQTime = TimeSecs()
|
|
2798
|
+
var p = GetMouseTargetPositionOnPlaneZ(0)
|
|
2799
|
+
SpawnEffect(Ori.alias.effect.explosion, p)
|
|
2800
|
+
var enemyTeam = 1 - myUnit.GetTeam()
|
|
2801
|
+
for t in p.GetObjectsWithinXYDistanceWithTeam(200, enemyTeam) {
|
|
2802
|
+
if t.IsSavedType(SavedObject.PlayerUnit) or t.IsSavedType(SavedObject.CreepUnit) {
|
|
2803
|
+
myUnit.Attack(t, 5)
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
IsAimingSkill = false
|
|
2808
|
+
}
|
|
2809
|
+
event RightMouseClickByThisPlayer {
|
|
2810
|
+
if IsAimingSkill { IsAimingSkill = false; return }
|
|
2811
|
+
|
|
2812
|
+
var myUnit = GetMyUnit()
|
|
2813
|
+
if myUnit == None { return }
|
|
2814
|
+
|
|
2815
|
+
var p = GetMouseTargetPositionOnPlaneZ(0)
|
|
2816
|
+
var lim = 580
|
|
2817
|
+
var goal = Position(p.X.Clamp(-lim, lim), p.Y.Clamp(-lim, lim), myUnit.GetPosition().Z)
|
|
2818
|
+
|
|
2819
|
+
// issue move order; unit drives itself in its Update (over-time builtins can't run on foreign refs)
|
|
2820
|
+
myUnit.MoveGoal = goal
|
|
2821
|
+
myUnit.HasMoveGoal = true
|
|
2822
|
+
}
|
|
2823
|
+
#end
|
|
2824
|
+
|
|
2825
|
+
|
|
2826
|
+
#class UIManager
|
|
2827
|
+
var ShopWindow: Object
|
|
2828
|
+
var ShopButton: Object
|
|
2829
|
+
var BuyHPButton: Object
|
|
2830
|
+
var BuyManaButton: Object
|
|
2831
|
+
var RosterRows: List
|
|
2832
|
+
|
|
2833
|
+
event OnSpawned {
|
|
2834
|
+
ShopWindow = Scene.ShopWindow
|
|
2835
|
+
ShopButton = Scene.ShopButton
|
|
2836
|
+
BuyHPButton = Scene.BuyHPButton
|
|
2837
|
+
BuyManaButton = Scene.BuyManaButton
|
|
2838
|
+
ShopWindow.SetVisible(false)
|
|
2839
|
+
|
|
2840
|
+
// Roster: cache the count-expanded rows once, then rebind names each second.
|
|
2841
|
+
RosterRows = []
|
|
2842
|
+
var i = 0
|
|
2843
|
+
while i < 6 { RosterRows.Add(FindChildUI("RosterRow" + i.ToText())); i += 1 }
|
|
2844
|
+
forever {
|
|
2845
|
+
var r = 0
|
|
2846
|
+
for p in GetAllPlayers() {
|
|
2847
|
+
if r >= RosterRows.Count() { break }
|
|
2848
|
+
var n = p.GetPlayerName()
|
|
2849
|
+
if n == "" { n = "Player " + (r + 1).ToText() } // validation/headless players have no username
|
|
2850
|
+
var c = Vector(0.4, 0.8, 1.0)
|
|
2851
|
+
if p.GetTeam() == 1 { c = Vector(1.0, 0.45, 0.4) }
|
|
2852
|
+
RosterRows.Get(r).SetText(n); RosterRows.Get(r).SetUiColor(c); RosterRows.Get(r).SetVisible(true)
|
|
2853
|
+
r += 1
|
|
2854
|
+
}
|
|
2855
|
+
while r < RosterRows.Count() { RosterRows.Get(r).SetVisible(false); r += 1 }
|
|
2856
|
+
Wait(1)
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
event ClickButton {
|
|
2860
|
+
var clicked = Event.ClickedButton
|
|
2861
|
+
var player = Event.ButtonClicker
|
|
2862
|
+
|
|
2863
|
+
if clicked == ShopButton {
|
|
2864
|
+
var opened = ShopWindow.IsVisible()
|
|
2865
|
+
ShopWindow.SetVisible(not opened)
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
if clicked == BuyHPButton {
|
|
2869
|
+
var cost = 10
|
|
2870
|
+
var myUnit = player.GetMyUnit()
|
|
2871
|
+
if myUnit.Exists() and player.Gold >= cost {
|
|
2872
|
+
player.Gold = player.Gold - cost
|
|
2873
|
+
var maxHP = 100 + (myUnit.Level * 10)
|
|
2874
|
+
myUnit.HP = maxHP
|
|
2875
|
+
myUnit.SetHoverHPBar(myUnit.HP, maxHP)
|
|
2876
|
+
myUnit.SpawnFloatingText("HP Full!", Vector(0, 1, 0))
|
|
2877
|
+
} else if myUnit.Exists() {
|
|
2878
|
+
myUnit.SpawnFloatingText("Not enough gold!", Vector(1, 1, 0))
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
if clicked == BuyManaButton {
|
|
2883
|
+
var cost = 5
|
|
2884
|
+
var myUnit = player.GetMyUnit()
|
|
2885
|
+
if myUnit.Exists() and player.Gold >= cost {
|
|
2886
|
+
player.Gold = player.Gold - cost
|
|
2887
|
+
var maxMP = 100 + (myUnit.Level * 5)
|
|
2888
|
+
myUnit.MP = maxMP
|
|
2889
|
+
myUnit.SetHoverBar(1, myUnit.MP, maxMP, Vector(0.2, 0.6, 1.0), Vector(0.1, 0.1, 0.2), 0, 8, 50, 6)
|
|
2890
|
+
myUnit.SpawnFloatingText("MP Full!", Vector(0.2, 0.6, 1.0))
|
|
2891
|
+
} else if myUnit.Exists() {
|
|
2892
|
+
myUnit.SpawnFloatingText("Not enough gold!", Vector(1, 1, 0))
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
#end
|
|
2897
|
+
|
|
2898
|
+
|
|
2899
|
+
material GroundMat { base = "StylizedGrass" }
|
|
2900
|
+
object ground { model = "cube" material = "GroundMat" position=(0, 0, -100) size=(10000, 10000, 100) }
|
|
2901
|
+
display Manager0 : GameManager { visible = false }
|
|
2902
|
+
|
|
2903
|
+
saved Checkpoint {
|
|
2904
|
+
region mark { model="alias.model.checkpoint" size=(20, 20, 60) }
|
|
2905
|
+
}
|
|
2906
|
+
saved WallCube {
|
|
2907
|
+
object cube { model="alias.model.wall" size=(40, 40, 80) }
|
|
2908
|
+
}
|
|
2909
|
+
saved LaneCube {
|
|
2910
|
+
object cube { model="alias.model.lane" size=(80, 20, 6) }
|
|
2911
|
+
}
|
|
2912
|
+
saved PlayerUnit {
|
|
2913
|
+
object hero : UnitStats { model="alias.model.player_unit" size=40 }
|
|
2914
|
+
}
|
|
2915
|
+
saved CreepUnit {
|
|
2916
|
+
crowd sphere : CreepBrain { model="alias.model.creep" size=20 }
|
|
2917
|
+
}
|
|
2918
|
+
saved Projectile {
|
|
2919
|
+
object sphere : ProjectileBrain { model="alias.model.projectile" size=6 }
|
|
2920
|
+
}
|
|
2921
|
+
saved TowerUnit {
|
|
2922
|
+
object tower : TowerBrain { model="alias.model.tower" size=(60, 60, 120) }
|
|
2923
|
+
}
|
|
2924
|
+
saved TowerUnitR350 {
|
|
2925
|
+
object tower : TowerBrain {
|
|
2926
|
+
model="alias.model.tower"
|
|
2927
|
+
size=(60, 60, 120)
|
|
2928
|
+
override AttackRange=350
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
screenCanvas MainUI : UIManager {
|
|
2933
|
+
// Team roster (top-left): who is in the match, colored by team.
|
|
2934
|
+
vStack Roster {
|
|
2935
|
+
offset = (20, 20)
|
|
2936
|
+
padding = 8
|
|
2937
|
+
gap = 4
|
|
2938
|
+
color = (0.1, 0.1, 0.14)
|
|
2939
|
+
textLabel RosterRow { count = 6 size = (200, 22) font_size = 14 }
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// vStack title over a row of buy buttons; no size = hug to fit content.
|
|
2943
|
+
vStack ShopWindow {
|
|
2944
|
+
offset = (1200, 600)
|
|
2945
|
+
padding = 10
|
|
2946
|
+
gap = 10
|
|
2947
|
+
color = (0.2, 0.2, 0.25)
|
|
2948
|
+
textLabel ShopTitle { size = (180, 30) text = "Item Shop" }
|
|
2949
|
+
hStack BuyRow {
|
|
2950
|
+
gap = (20, 0)
|
|
2951
|
+
button BuyHPButton { size = (80, 80) text = "HP (10G)" }
|
|
2952
|
+
button BuyManaButton { size = (80, 80) text = "MP (5G)" }
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// hStack lays the skill buttons across; no size = hug (-> 370x100). Bottom-center anchored ability bar.
|
|
2957
|
+
hStack SkillBar {
|
|
2958
|
+
anchor = (center, bottom)
|
|
2959
|
+
offset = (0, -20)
|
|
2960
|
+
padding = 10
|
|
2961
|
+
gap = 10
|
|
2962
|
+
button SkillButtonQ { size = (80, 80) color = (0.1, 0.1, 0.1) text = "Q (10)" }
|
|
2963
|
+
button SkillButtonW { size = (80, 80) color = (0.1, 0.1, 0.1) text = "W" }
|
|
2964
|
+
button SkillButtonE { size = (80, 80) color = (0.1, 0.1, 0.1) text = "E" }
|
|
2965
|
+
button SkillButtonR { size = (80, 80) color = (0.1, 0.1, 0.1) text = "R" }
|
|
2966
|
+
}
|
|
2967
|
+
frame ShopPanel {
|
|
2968
|
+
anchor = (right, bottom)
|
|
2969
|
+
offset = (-20, -20)
|
|
2970
|
+
size = (140, 100)
|
|
2971
|
+
button ShopButton {
|
|
2972
|
+
offset = (10, 10)
|
|
2973
|
+
size = (120, 80)
|
|
2974
|
+
textLabel ShopText {
|
|
2975
|
+
text = "SHOP"
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
|
|
2982
|
+
</example_dota>
|
|
2983
|
+
|
|
2984
|
+
|
|
2985
|
+
<example_fps_viewmodel>
|
|
2986
|
+
// Minimal FPS viewmodel skeleton — the engine defaults do the genre work:
|
|
2987
|
+
// EquipWeapon seats the hand on the library rifle's grip socket, the built-in canon
|
|
2988
|
+
// hold poses the first-person arms (hands + a sliver of forearm), and
|
|
2989
|
+
// EnableLocalWeaponView(Offset, RotationDeg, Scale, FovDegrees) renders weapon+arms
|
|
2990
|
+
// through the dedicated viewmodel pass (own FOV, never clips into walls).
|
|
2991
|
+
// No SetWeaponConfig, no anchors, no clips required.
|
|
2992
|
+
|
|
2993
|
+
#class player
|
|
2994
|
+
var T: Number
|
|
2995
|
+
|
|
2996
|
+
event OnSpawned {
|
|
2997
|
+
SetCameraToFirstPerson()
|
|
2998
|
+
EnableAutoMoveAnimations() // optional (walk/run clips while moving) - the viewmodel renders without it
|
|
2999
|
+
EquipWeapon(Ori.alias.model.rifle)
|
|
3000
|
+
EnableLocalWeaponView(Position(15, -22, 38), Vector(2, 4, 0), 1.0, 55)
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
event Update {
|
|
3004
|
+
// Walk into the wall ahead: arms stay held, the viewmodel never clips.
|
|
3005
|
+
T = T + 1
|
|
3006
|
+
if T > 120 {
|
|
3007
|
+
if T < 420 {
|
|
3008
|
+
MoveInDirectionContinuously(Vector(0, 1, 0), 620)
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
#end
|
|
3013
|
+
|
|
3014
|
+
object Ground {
|
|
3015
|
+
model = "cube"
|
|
3016
|
+
size = (2400, 2400, 20)
|
|
3017
|
+
position = (0, 0, -10)
|
|
3018
|
+
color = (0.45, 0.5, 0.4)
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
object Wall {
|
|
3022
|
+
model = "cube"
|
|
3023
|
+
size = (800, 60, 300)
|
|
3024
|
+
position = (0, 300, 150)
|
|
3025
|
+
color = (0.6, 0.55, 0.5)
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
</example_fps_viewmodel>
|
|
3029
|
+
|
|
3030
|
+
|
|
3031
|
+
<example_grass_meadow_adjuster_demo>
|
|
3032
|
+
// Grass meadow: built-in tuft-card grass on gentle hills, all global grass/ground
|
|
3033
|
+
// look knobs declared (values apply on load; sliders live in the Terrain inspector -
|
|
3034
|
+
// click the terrain; Ctrl+S folds edits back into this block). Ground under grass auto-tints.
|
|
3035
|
+
|
|
3036
|
+
#adjuster
|
|
3037
|
+
section "Grass"
|
|
3038
|
+
knob SetGrassRootColor = #41591c
|
|
3039
|
+
knob SetGrassTipColor = #93b944
|
|
3040
|
+
knob SetGrassBrightnessVariationPercent = 120
|
|
3041
|
+
knob SetGrassSizeVariationPercent = 100
|
|
3042
|
+
knob SetGrassHeightMinCm = 20
|
|
3043
|
+
knob SetGrassHeightMaxCm = 90
|
|
3044
|
+
knob SetGrassWavePeriodCm = 700
|
|
3045
|
+
knob SetGrassWindStrengthCm = 4 range 0..30
|
|
3046
|
+
knob SetGrassWindSpeedPercent = 100 range 0..300
|
|
3047
|
+
knob SetGrassBladeWidthPercent = 100
|
|
3048
|
+
knob SetGrassSpacingCm = 20 range 0..60
|
|
3049
|
+
#end
|
|
3050
|
+
|
|
3051
|
+
// Tuft-card atlas (replaces the built-in one via SetTerrainGrass material): 2x2 variant
|
|
3052
|
+
// cells, GREYSCALE luminance = root->tip gradient; color comes from the grass color knobs.
|
|
3053
|
+
#texture GrassTufts
|
|
3054
|
+
mapping = uv;
|
|
3055
|
+
wrap = clamp;
|
|
3056
|
+
render_mode = cutout;
|
|
3057
|
+
param blades = 34.0 range 12.0..60.0;
|
|
3058
|
+
let vid = floor(uv.x * 2.0) + floor(uv.y * 2.0) * 2.0;
|
|
3059
|
+
let lu = fract(uv.x * 2.0);
|
|
3060
|
+
let g = 1.0 - fract(uv.y * 2.0);
|
|
3061
|
+
let fan = (lu - 0.5) * (0.3 + 0.3 * rand(vid, seed: 3.0)) * g * g;
|
|
3062
|
+
let x = lu - fan;
|
|
3063
|
+
let id = floor(x * blades) + vid * 977.0;
|
|
3064
|
+
let bell = sat(1.0 - abs((floor(x * blades) + 0.5) / blades - 0.5) * 2.4);
|
|
3065
|
+
let h = (0.25 + 0.70 * rand(id)) * (0.25 + 0.75 * bell);
|
|
3066
|
+
let t = sat((g - 0.06) / max(h - 0.06, 0.05));
|
|
3067
|
+
let curve = (rand(id, seed: 13.0) - 0.5) * 0.55 * t * t;
|
|
3068
|
+
let w = (0.34 + 0.30 * rand(id, seed: 7.0)) * (1.0 - 0.75 * t * t) * sat(t * 7.0);
|
|
3069
|
+
let strip = 1.0 - smoothstep(w, w + 0.22, abs(fract(x * blades) - 0.5 + curve) * 2.0);
|
|
3070
|
+
let a = strip * (1.0 - smoothstep(h - 0.10, h, g)) * step(0.08, h) * smoothstep(0.0, 0.05, t);
|
|
3071
|
+
out.color = ramp(t, [0.0: color("#0a0a0a"), 1.0: color("#e6e6e6")]) * (0.85 + 0.30 * rand(id, seed: 5.0));
|
|
3072
|
+
out.alpha = sat(a) * smoothstep(0.01, 0.05, lu) * (1.0 - smoothstep(0.95, 0.99, lu));
|
|
3073
|
+
out.orm = orm(occlusion: 1.0, roughness: 0.85, metallic: 0.0);
|
|
3074
|
+
out.normal = normal_from_height(g * 0.0, strength: 1.0);
|
|
3075
|
+
#end
|
|
3076
|
+
|
|
3077
|
+
#class MeadowWorld
|
|
3078
|
+
event OnSpawned {
|
|
3079
|
+
var heights: List<Number> = []
|
|
3080
|
+
heights.Resize(49 * 49, 0)
|
|
3081
|
+
TerrainHeightsNoise(heights, 49, 49, 0, 16, 3, 7, 220, 0) // gentle fbm hills
|
|
3082
|
+
var t = SpawnTerrainFromHeights(heights, 49, 49, 100, Origin())
|
|
3083
|
+
t.SetTerrainGrass(None, Material.GrassTufts) // (None, None) also fine: built-in atlas, same knobs
|
|
3084
|
+
}
|
|
3085
|
+
#end
|
|
3086
|
+
|
|
3087
|
+
display MeadowWorldObject : MeadowWorld { visible = false }
|
|
3088
|
+
|
|
3089
|
+
</example_grass_meadow_adjuster_demo>
|
|
3090
|
+
|
|
3091
|
+
|
|
3092
|
+
<example_grassy_eroded_hills>
|
|
3093
|
+
#class player
|
|
3094
|
+
event OnSpawned {
|
|
3095
|
+
forever {
|
|
3096
|
+
var Direction = GetMoveVectorCamera()
|
|
3097
|
+
MoveInDirectionContinuouslyFacing(Direction, 900)
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
event ActionDownByThisPlayer[Jump] {
|
|
3101
|
+
if IsOnGround() {
|
|
3102
|
+
Jump(1000)
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
#end
|
|
3106
|
+
|
|
3107
|
+
// grassy_eroded_hills: a complete outdoor scene from the eroded_mountain terrain preset -
|
|
3108
|
+
// rolling eroded hills as one lush grass field, lakes in the valleys, gentle water swell.
|
|
3109
|
+
// Pattern notes:
|
|
3110
|
+
// - A bare #terrain block (preset only) is a complete terrain; add keys to override.
|
|
3111
|
+
// - The visible water level is SetWaterEnabled's sea level. The recipe field never goes
|
|
3112
|
+
// below its own zero, so WorldGen scans the generated heights and drops the terrain so
|
|
3113
|
+
// the deepest valley sits 3m under the sea - that is what makes the lakes.
|
|
3114
|
+
// - One #texture ground surface: a single flat color + fine grain only. Low-frequency
|
|
3115
|
+
// color patches tile visibly at distance - avoid them.
|
|
3116
|
+
// - The baked ground normal scales live via SetTerrainNormalStrengthPercent.
|
|
3117
|
+
|
|
3118
|
+
#terrain GrassLand
|
|
3119
|
+
preset = eroded_mountain
|
|
3120
|
+
#end
|
|
3121
|
+
|
|
3122
|
+
#adjuster
|
|
3123
|
+
section "Grass"
|
|
3124
|
+
knob SetGrassTipColor = #71b23e
|
|
3125
|
+
knob SetGrassRootColor = #66a139
|
|
3126
|
+
knob SetGrassBrightnessVariationPercent = 25
|
|
3127
|
+
knob SetGrassHeightMaxCm = 100
|
|
3128
|
+
knob SetGrassHeightMinCm = 50
|
|
3129
|
+
knob SetGrassWavePeriodCm = 700
|
|
3130
|
+
knob SetGrassWindStrengthCm = 6 range 0..20
|
|
3131
|
+
section "Ground"
|
|
3132
|
+
knob SetTerrainNormalStrengthPercent = 20 range 0..300
|
|
3133
|
+
#end
|
|
3134
|
+
|
|
3135
|
+
#texture GrassGround
|
|
3136
|
+
tile_m = 4.0;
|
|
3137
|
+
let fine = noise.fbm(uv * 20.0, octaves: 4, seed: 7, period: 20);
|
|
3138
|
+
let micro = noise.fbm(uv * 64.0, octaves: 2, seed: 9, period: 64);
|
|
3139
|
+
out.color = color("#73aa3e") * (0.96 + fine * 0.05 + micro * 0.03);
|
|
3140
|
+
out.height = fine * 0.6 + micro * 0.4;
|
|
3141
|
+
out.normal = normal_from_height(fine * 0.5 + micro * 0.5, strength: 0.5);
|
|
3142
|
+
out.orm = orm(occlusion: 1.0, roughness: 0.95, metallic: 0.0);
|
|
3143
|
+
#end
|
|
3144
|
+
|
|
3145
|
+
#terrain_surfaces GrassFieldSurfaces
|
|
3146
|
+
surface = baked.GrassGround.color, baked.GrassGround.normal, 4.0, baked.GrassGround.orm, 1.0
|
|
3147
|
+
#end
|
|
3148
|
+
|
|
3149
|
+
// One surface everywhere (weights pick surface 0; grass density = its affinity 1.0).
|
|
3150
|
+
#shader GrassFieldWeights
|
|
3151
|
+
fn ori_surface_weights(ctx: OriSurfaceWeightsCtx) -> OriSurfaceWeights {
|
|
3152
|
+
var w: OriSurfaceWeights;
|
|
3153
|
+
w.w0 = vec4<f32>(1.0, 0.0, 0.0, 0.0);
|
|
3154
|
+
w.w1 = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
3155
|
+
return w;
|
|
3156
|
+
}
|
|
3157
|
+
#end
|
|
3158
|
+
|
|
3159
|
+
material GrassFieldWeightsMat { shader = "GrassFieldWeights" }
|
|
3160
|
+
|
|
3161
|
+
#class WorldGen
|
|
3162
|
+
event OnSpawned {
|
|
3163
|
+
var heights: List<Number> = []
|
|
3164
|
+
var ridges: List<Number> = []
|
|
3165
|
+
TerrainHeightsFromTerrain(heights, ridges, 192, 192, 150)
|
|
3166
|
+
var minH = 99999999
|
|
3167
|
+
for h in heights {
|
|
3168
|
+
if h < minH { minH = h }
|
|
3169
|
+
}
|
|
3170
|
+
var terrain = SpawnTerrainFromHeights(heights, 192, 192, 150, Position(0, 0, 0 - minH - 300))
|
|
3171
|
+
terrain.SetTerrainSurfaces("GrassFieldSurfaces")
|
|
3172
|
+
terrain.SetTerrainSurfaceWeightsMaterial(Material.GrassFieldWeightsMat)
|
|
3173
|
+
SetTerrainDisplayExtension(3000)
|
|
3174
|
+
SetCameraFarDistanceM(3000)
|
|
3175
|
+
SetFogEndDistance(2800)
|
|
3176
|
+
SetSunTilt(33)
|
|
3177
|
+
SetSunRotation(35)
|
|
3178
|
+
SetWaterEnabled(true, 0)
|
|
3179
|
+
SetWaterWaves(5, 800, 0.3, 0, 100)
|
|
3180
|
+
}
|
|
3181
|
+
#end
|
|
3182
|
+
|
|
3183
|
+
display WorldGenObj : WorldGen { visible = false }
|
|
3184
|
+
|
|
3185
|
+
</example_grassy_eroded_hills>
|
|
3186
|
+
|
|
3187
|
+
|
|
3188
|
+
<example_saved_block_font>
|
|
3189
|
+
// Declarative fonts work inside saved blocks (font = "font.google.<Family>" +
|
|
3190
|
+
// font_size); spawned copies keep the custom font.
|
|
3191
|
+
#class player
|
|
3192
|
+
event OnSpawned {
|
|
3193
|
+
SetDefaultPlayerControlsEnabled(true)
|
|
3194
|
+
var hud = SpawnCanvas(SavedObject.StyledHud, Origin())
|
|
3195
|
+
hud.SetPlayerOwnerIndex(GetPlayerIndex())
|
|
3196
|
+
}
|
|
3197
|
+
#end
|
|
3198
|
+
|
|
3199
|
+
object Ground { model = "cube" size = (4000, 4000, 20) position = (0, 0, -10) }
|
|
3200
|
+
|
|
3201
|
+
saved StyledHud {
|
|
3202
|
+
screenCanvas C {
|
|
3203
|
+
vStack Panel { offset = (40, 40) padding = 12 gap = 8 color = #1E1E1EF0
|
|
3204
|
+
textLabel T { size = (360, 80) text = "Styled" font = "font.google.Lobster" font_size = 40 }
|
|
3205
|
+
button B { size = (260, 70) text = "Ok" font = "font.google.Roboto_Mono" font_size = 24 }
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
</example_saved_block_font>
|
|
3211
|
+
|
|
3212
|
+
|
|
3213
|
+
<example_stardew_valley>
|
|
3214
|
+
// Stardew-like Farming Prototype: engine inventory (item clauses + BindInventory hotbar with
|
|
3215
|
+
// engine-side drag/swap), tools, crops, resources, drops, selling, energy/day cycle.
|
|
3216
|
+
// Pattern notes:
|
|
3217
|
+
// - Items are `item` clauses; the hotbar is ONE empty hStack + BindInventory (engine spawns
|
|
3218
|
+
// synced draggable slots; SetInventorySelectable gives hotbar selection). No slot markup,
|
|
3219
|
+
// no per-slot refresh code, no hand-rolled drag: drops land in InventoryDropOnWorld.
|
|
3220
|
+
// - Multiplayer: shared farm world (crops/tools/drops are shared and per-player-interactable);
|
|
3221
|
+
// Bag is per-player (created in player OnSpawned).
|
|
3222
|
+
// Caveat: the gold/energy/day HUD and shop are single shared canvases (single-player-shaped UI).
|
|
3223
|
+
#set TileSize = 100
|
|
3224
|
+
|
|
3225
|
+
// Bare aliases (no target): auto-bound from the generated model / UI-image libraries by name.
|
|
3226
|
+
alias model.soil_tile
|
|
3227
|
+
alias model.cabbage
|
|
3228
|
+
alias model.carrot
|
|
3229
|
+
alias model.grape
|
|
3230
|
+
alias model.chest
|
|
3231
|
+
alias model.shop_counter
|
|
3232
|
+
alias model.flat_rock_slab
|
|
3233
|
+
alias model.trunk
|
|
3234
|
+
alias model.grass_rock_cliff
|
|
3235
|
+
alias model.green_maple_tree
|
|
3236
|
+
alias model.bed
|
|
3237
|
+
alias model.ground_item
|
|
3238
|
+
alias model.held_item
|
|
3239
|
+
|
|
3240
|
+
alias image.gold_icon
|
|
3241
|
+
alias image.energy_icon
|
|
3242
|
+
// One icon per item type (referenced by the item clauses below).
|
|
3243
|
+
alias image.hoe_icon
|
|
3244
|
+
alias image.parsnip_seeds_icon
|
|
3245
|
+
alias image.parsnip_icon
|
|
3246
|
+
alias image.watering_can_icon
|
|
3247
|
+
alias image.pickaxe_icon
|
|
3248
|
+
alias image.axe_icon
|
|
3249
|
+
alias image.scythe_icon
|
|
3250
|
+
alias image.fishing_rod_icon
|
|
3251
|
+
alias image.wood_icon
|
|
3252
|
+
alias image.stone_icon
|
|
3253
|
+
alias image.potato_seeds_icon
|
|
3254
|
+
alias image.bean_seeds_icon
|
|
3255
|
+
alias image.potato_icon
|
|
3256
|
+
alias image.bean_icon
|
|
3257
|
+
|
|
3258
|
+
// Item definitions: name/tip/icon show in the bound hotbar; stack= caps per-slot count;
|
|
3259
|
+
// value= is what the shipping bin pays (no value = not sellable).
|
|
3260
|
+
item hoe { name="Hoe" tip="Till green plots into soil." icon="alias.image.hoe_icon" tint=(0.7,0.7,0.9) }
|
|
3261
|
+
item watering_can { name="Watering Can" tip="Water planted crops to speed growth. Refill at the pond." icon="alias.image.watering_can_icon" tint=(0.2,0.55,1.0) }
|
|
3262
|
+
item pickaxe { name="Pickaxe" tip="Break rocks for stone." icon="alias.image.pickaxe_icon" tint=(0.65,0.65,0.65) }
|
|
3263
|
+
item axe { name="Axe" tip="Chop trees for wood." icon="alias.image.axe_icon" tint=(0.75,0.35,0.12) }
|
|
3264
|
+
item scythe { name="Scythe" tip="Harvest mature crops quickly." icon="alias.image.scythe_icon" tint=(0.55,1.0,0.55) }
|
|
3265
|
+
item fishing_rod { name="Fishing Rod" tip="A classic Stardew tool placeholder." icon="alias.image.fishing_rod_icon" tint=(0.7,0.45,0.2) }
|
|
3266
|
+
item parsnip_seeds { name="Parsnip Seeds" tip="Plant on tilled soil. Grows after watering/time." icon="alias.image.parsnip_seeds_icon" tint=(0.2,0.9,0.2) stack=99 value=2 }
|
|
3267
|
+
item potato_seeds { name="Potato Seeds" tip="Plant on tilled soil. Sells for more than parsnips." icon="alias.image.potato_seeds_icon" tint=(0.75,0.55,0.25) stack=99 }
|
|
3268
|
+
item bean_seeds { name="Bean Seeds" tip="Plant on tilled soil. High value crop." icon="alias.image.bean_seeds_icon" tint=(0.15,0.75,0.35) stack=99 }
|
|
3269
|
+
item parsnip { name="Parsnip" tip="Crop. Drag to the shipping bin to sell for 20g." icon="alias.image.parsnip_icon" tint=(1.0,0.85,0.15) stack=99 value=20 }
|
|
3270
|
+
item potato { name="Potato" tip="Crop. Drag to the shipping bin to sell for 35g." icon="alias.image.potato_icon" tint=(0.85,0.65,0.28) stack=99 value=35 }
|
|
3271
|
+
item bean { name="Bean" tip="Crop. Drag to the shipping bin to sell for 50g." icon="alias.image.bean_icon" tint=(0.15,0.9,0.4) stack=99 value=50 }
|
|
3272
|
+
item wood { name="Wood" tip="Basic crafting material. Drag to the ground to drop." icon="alias.image.wood_icon" tint=(0.45,0.22,0.08) stack=99 value=1 }
|
|
3273
|
+
item stone { name="Stone" tip="Basic crafting material. Drag to the ground to drop." icon="alias.image.stone_icon" tint=(0.55,0.55,0.55) stack=99 value=1 }
|
|
3274
|
+
|
|
3275
|
+
#class player
|
|
3276
|
+
var Money = 100
|
|
3277
|
+
var Energy = 100
|
|
3278
|
+
var MaxEnergy = 100
|
|
3279
|
+
var Water = 0
|
|
3280
|
+
var MaxWater = 20
|
|
3281
|
+
var Day = 1
|
|
3282
|
+
var Sleeping = false
|
|
3283
|
+
|
|
3284
|
+
var Bag: Object
|
|
3285
|
+
var LastSelected: Object
|
|
3286
|
+
var HeldVisual: Object
|
|
3287
|
+
var UseAnimFrames = 0
|
|
3288
|
+
|
|
3289
|
+
var StatsLabel: Object
|
|
3290
|
+
var EnergyLabel: Object
|
|
3291
|
+
var DayLabel: Object
|
|
3292
|
+
var SleepFade: Object
|
|
3293
|
+
var DayToast: Object
|
|
3294
|
+
|
|
3295
|
+
var UsedHoe = false
|
|
3296
|
+
var UsedSeeds = false
|
|
3297
|
+
var UsedWater = false
|
|
3298
|
+
var UsedPickaxe = false
|
|
3299
|
+
var UsedAxe = false
|
|
3300
|
+
var UsedScythe = false
|
|
3301
|
+
var UsedFishing = false
|
|
3302
|
+
var HasHarvested = false
|
|
3303
|
+
var SleepMoveAllowed = false
|
|
3304
|
+
|
|
3305
|
+
var ShopPanel: Object
|
|
3306
|
+
var ShopMessage: Object
|
|
3307
|
+
|
|
3308
|
+
func SelectedTool() -> Object {
|
|
3309
|
+
return Bag.ItemAt(Bag.GetSelectedIndex())
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
func IsToolLike(it: Object) -> Bool {
|
|
3313
|
+
if it == Item.hoe or it == Item.watering_can or it == Item.pickaxe or it == Item.axe { return true }
|
|
3314
|
+
if it == Item.scythe or it == Item.fishing_rod { return true }
|
|
3315
|
+
if it == Item.parsnip_seeds or it == Item.potato_seeds or it == Item.bean_seeds { return true }
|
|
3316
|
+
return false
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
func FirstTimeHint(it: Object) -> Text {
|
|
3320
|
+
if it == Item.hoe and not UsedHoe { return "Hoe: face a green plot, then click anywhere." }
|
|
3321
|
+
if it == Item.parsnip_seeds and not UsedSeeds { return "Seeds: click empty tilled soil to plant." }
|
|
3322
|
+
if it == Item.watering_can and not UsedWater { return "Watering Can: refill at pond, then face crops and click." }
|
|
3323
|
+
if it == Item.pickaxe and not UsedPickaxe { return "Pickaxe: click rocks to break them." }
|
|
3324
|
+
if it == Item.axe and not UsedAxe { return "Axe: click trees to chop wood." }
|
|
3325
|
+
if it == Item.scythe and not UsedScythe { return "Scythe: click mature crops to harvest." }
|
|
3326
|
+
if it == Item.fishing_rod and not UsedFishing { return "Fishing Rod: try it by the pond (F)." }
|
|
3327
|
+
return ""
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
// Add with overflow feedback; the engine fills existing stacks first.
|
|
3331
|
+
func FarmAddItem(it: Object, count: Number) -> Bool {
|
|
3332
|
+
if it == None or count <= 0 { return false }
|
|
3333
|
+
var overflow = Bag.AddItem(it, count)
|
|
3334
|
+
if overflow > 0 {
|
|
3335
|
+
SpawnFloatingText("Inventory full", Vector(1, 1, 0))
|
|
3336
|
+
if overflow == count { return false }
|
|
3337
|
+
}
|
|
3338
|
+
return true
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
// Remove one of the selected slot's item (seeds on planting).
|
|
3342
|
+
func ConsumeSelectedOne() -> Bool {
|
|
3343
|
+
var slot = Bag.GetSelectedIndex()
|
|
3344
|
+
var it = Bag.ItemAt(slot)
|
|
3345
|
+
var c = Bag.CountAt(slot)
|
|
3346
|
+
if it == None or c <= 0 { return false }
|
|
3347
|
+
Bag.SetAt(slot, it, c - 1)
|
|
3348
|
+
return true
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
func RefreshHud() {
|
|
3352
|
+
var waterText = ""
|
|
3353
|
+
if SelectedTool() == Item.watering_can { waterText = "\n" + Water + "/" + MaxWater }
|
|
3354
|
+
StatsLabel.SetText(Money.ToText())
|
|
3355
|
+
EnergyLabel.SetText(Energy + "/" + MaxEnergy + waterText)
|
|
3356
|
+
DayLabel.SetText("Day " + Day)
|
|
3357
|
+
if SelectedTool() == Item.watering_can { SetHoverSPBar(Water, MaxWater) } else { ClearHoverBars() }
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
func ShowPickupItem(it: Object) {
|
|
3361
|
+
SpawnFloatingText("+" + ItemName(it), ItemTint(it))
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
func DropItemInFront(it: Object, count: Number) {
|
|
3365
|
+
var pos = GetPositionFront(70)
|
|
3366
|
+
var d = SpawnObject(SavedObject.groundItem, Position(pos.X, pos.Y, 70))
|
|
3367
|
+
d.ItemDef = it
|
|
3368
|
+
d.Count = count
|
|
3369
|
+
d.RefreshDrop()
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
func BuySeed(it: Object, count: Number, cost: Number) {
|
|
3373
|
+
if Money < cost {
|
|
3374
|
+
ShopMessage.SetText("Not enough money")
|
|
3375
|
+
ShopMessage.SetUiColor(Vector(1, 0.1, 0.1))
|
|
3376
|
+
return
|
|
3377
|
+
}
|
|
3378
|
+
if FarmAddItem(it, count) {
|
|
3379
|
+
Money -= cost
|
|
3380
|
+
ShopMessage.SetText("Bought " + ItemName(it))
|
|
3381
|
+
ShopMessage.SetUiColor(Vector(0.3, 1, 0.3))
|
|
3382
|
+
SpawnFloatingText("Bought " + ItemName(it), ItemTint(it))
|
|
3383
|
+
RefreshHud()
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
event OnSpawned {
|
|
3388
|
+
SetCameraToTopDown()
|
|
3389
|
+
SetCameraZoom(900)
|
|
3390
|
+
SetCameraTiltDown(45)
|
|
3391
|
+
TeleportToTile(0, -4)
|
|
3392
|
+
|
|
3393
|
+
Bag = CreateInventory(12)
|
|
3394
|
+
Bag.AddItem(Item.hoe, 1)
|
|
3395
|
+
Bag.AddItem(Item.watering_can, 1)
|
|
3396
|
+
Bag.AddItem(Item.pickaxe, 1)
|
|
3397
|
+
Bag.AddItem(Item.axe, 1)
|
|
3398
|
+
Bag.AddItem(Item.scythe, 1)
|
|
3399
|
+
Bag.AddItem(Item.parsnip_seeds, 9)
|
|
3400
|
+
Bag.AddItem(Item.fishing_rod, 1)
|
|
3401
|
+
Scene.Hotbar.BindInventory(Bag) // engine spawns synced draggable slots
|
|
3402
|
+
Scene.Hotbar.SetInventorySelectable(true) // click a slot to equip it
|
|
3403
|
+
|
|
3404
|
+
StatsLabel = Scene.GoldText
|
|
3405
|
+
EnergyLabel = Scene.EnergyText
|
|
3406
|
+
DayLabel = Scene.DayText
|
|
3407
|
+
SleepFade = Scene.SleepFade
|
|
3408
|
+
DayToast = Scene.DayToast
|
|
3409
|
+
ShopPanel = Scene.ShopPanel
|
|
3410
|
+
ShopMessage = Scene.ShopMessage
|
|
3411
|
+
Scene.GoldIcon.SetUiImage(Ori.alias.image.gold_icon)
|
|
3412
|
+
Scene.EnergyIcon.SetUiImage(Ori.alias.image.energy_icon)
|
|
3413
|
+
|
|
3414
|
+
ShopPanel.SetVisible(false)
|
|
3415
|
+
SleepFade.SetVisible(false)
|
|
3416
|
+
SleepFade.SetUiOpacity(0)
|
|
3417
|
+
DayToast.SetVisible(false)
|
|
3418
|
+
HeldVisual = SpawnObject(SavedObject.heldItemVisual, GetPositionFront(80).Up(25))
|
|
3419
|
+
HeldVisual.SetVisible(false)
|
|
3420
|
+
RefreshHud()
|
|
3421
|
+
|
|
3422
|
+
forever {
|
|
3423
|
+
var dir = GetMoveVectorWorld()
|
|
3424
|
+
if not Sleeping or SleepMoveAllowed { MoveInDirectionContinuouslyFacing(dir, 350) }
|
|
3425
|
+
|
|
3426
|
+
var sel = SelectedTool()
|
|
3427
|
+
if sel != LastSelected {
|
|
3428
|
+
LastSelected = sel
|
|
3429
|
+
if sel != None { SpawnFloatingText("Equipped " + ItemName(sel), ItemTint(sel)) }
|
|
3430
|
+
RefreshHud()
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
if sel != None and IsToolLike(sel) {
|
|
3434
|
+
HeldVisual.SetVisible(true)
|
|
3435
|
+
HeldVisual.SetColor(ItemTint(sel))
|
|
3436
|
+
var phase = 0
|
|
3437
|
+
var push = 0
|
|
3438
|
+
var lift = 18
|
|
3439
|
+
var scale = 24
|
|
3440
|
+
if UseAnimFrames > 0 {
|
|
3441
|
+
phase = UseAnimFrames
|
|
3442
|
+
if phase > 6 { phase = 12 - phase }
|
|
3443
|
+
push = phase * 5
|
|
3444
|
+
lift = 18 - phase * 3
|
|
3445
|
+
scale = 24 + phase * 12.7
|
|
3446
|
+
UseAnimFrames -= 1
|
|
3447
|
+
}
|
|
3448
|
+
if phase > 0 {
|
|
3449
|
+
HeldVisual.SetSize(scale, scale, 6)
|
|
3450
|
+
var area = GetPositionFront(32 + push)
|
|
3451
|
+
HeldVisual.SetPosition(Position(area.X, area.Y, lift.Max(0)))
|
|
3452
|
+
} else {
|
|
3453
|
+
HeldVisual.SetSize(24, 24, 24)
|
|
3454
|
+
HeldVisual.SetPosition(GetPositionFront(32).Up(18))
|
|
3455
|
+
}
|
|
3456
|
+
var hint = FirstTimeHint(sel)
|
|
3457
|
+
if hint != "" { Say(hint) } else { ClearSay() }
|
|
3458
|
+
} else {
|
|
3459
|
+
if HeldVisual.Exists() { HeldVisual.SetVisible(false) }
|
|
3460
|
+
ClearSay()
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
var bed = NearestObjectWithTag(Tag.Bed)
|
|
3464
|
+
if bed.Exists() and SurfaceGapXYTo(bed) < 140 {
|
|
3465
|
+
bed.Say("Press E to sleep")
|
|
3466
|
+
} else {
|
|
3467
|
+
if bed.Exists() { bed.ClearSay() }
|
|
3468
|
+
}
|
|
3469
|
+
var counter = NearestObjectWithTag(Tag.ShopCounter)
|
|
3470
|
+
if counter.Exists() and SurfaceGapXYTo(counter) < 180 {
|
|
3471
|
+
counter.Say("Tap/use counter to buy seeds")
|
|
3472
|
+
} else {
|
|
3473
|
+
if counter.Exists() { counter.ClearSay() }
|
|
3474
|
+
}
|
|
3475
|
+
for plotHint in GetAllObjectsWithTag(Tag.Plot) {
|
|
3476
|
+
if not HasHarvested and plotHint.CropStage == 4 and SurfaceGapXYTo(plotHint) < 220 {
|
|
3477
|
+
plotHint.Say("Tap/use to harvest")
|
|
3478
|
+
} else {
|
|
3479
|
+
plotHint.ClearSay()
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
event ActionDownByThisPlayer[Primary] {
|
|
3486
|
+
if Scene.Hotbar.IsMouseOnUi() { return } // clicking the hotbar selects/drags, not uses
|
|
3487
|
+
UseAnimFrames = 12
|
|
3488
|
+
var sel = SelectedTool()
|
|
3489
|
+
var front = GetPositionFront(90)
|
|
3490
|
+
var readyPlot = front.NearestObjectWithTag(Tag.Plot)
|
|
3491
|
+
if readyPlot.Exists() and front.SurfaceGapXYTo(readyPlot) < 130 and readyPlot.CropStage == 4 {
|
|
3492
|
+
readyPlot.ApplyTool(Self)
|
|
3493
|
+
return
|
|
3494
|
+
}
|
|
3495
|
+
if sel == Item.hoe or sel == Item.watering_can or sel == Item.scythe or sel == Item.parsnip_seeds or sel == Item.potato_seeds or sel == Item.bean_seeds {
|
|
3496
|
+
var plot = front.NearestObjectWithTag(Tag.Plot)
|
|
3497
|
+
if plot.Exists() and front.SurfaceGapXYTo(plot) < 130 {
|
|
3498
|
+
plot.ApplyTool(Self)
|
|
3499
|
+
} else if sel == Item.hoe {
|
|
3500
|
+
SpawnFloatingText("Stand close and face a green plot", Vector(1, 1, 0))
|
|
3501
|
+
} else if sel == Item.watering_can {
|
|
3502
|
+
var pond = front.NearestObjectWithTag(Tag.Pond)
|
|
3503
|
+
if pond.Exists() and front.SurfaceGapXYTo(pond) < 125 {
|
|
3504
|
+
Water = MaxWater
|
|
3505
|
+
UsedWater = true
|
|
3506
|
+
SpawnFloatingText("Water refilled", Vector(0.2, 0.6, 1))
|
|
3507
|
+
RefreshHud()
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
} else if sel == Item.pickaxe or sel == Item.axe {
|
|
3511
|
+
var res = front.NearestObjectWithTag(Tag.Resource)
|
|
3512
|
+
if res.Exists() and front.DistanceXYTo(res) < 85 { res.ApplyTool(Self) }
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
// Bound-slot drag released over the world: sell on the shipping bin, else drop at the feet.
|
|
3517
|
+
// The engine does not mutate the inventory here — the game decides (clear the slot ourselves).
|
|
3518
|
+
event InventoryDropOnWorld {
|
|
3519
|
+
if Event.DropObject.Exists() and Event.DropObject.HasTag(Tag.SellBox) {
|
|
3520
|
+
var value = ItemValue(Event.Item) * Event.Count
|
|
3521
|
+
if value > 0 {
|
|
3522
|
+
Money += value
|
|
3523
|
+
SpawnFloatingText("Sold for " + value + "g", Vector(1, 0.9, 0.1))
|
|
3524
|
+
Bag.SetAt(Event.SlotIndex, Event.Item, 0)
|
|
3525
|
+
RefreshHud()
|
|
3526
|
+
} else {
|
|
3527
|
+
SpawnFloatingText("That cannot be sold", Vector(1, 0.4, 0.2))
|
|
3528
|
+
}
|
|
3529
|
+
return
|
|
3530
|
+
}
|
|
3531
|
+
DropItemInFront(Event.Item, Event.Count)
|
|
3532
|
+
Bag.SetAt(Event.SlotIndex, Event.Item, 0)
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
event ClickButton {
|
|
3536
|
+
var clicked = Event.ClickedButton
|
|
3537
|
+
if clicked == Scene.BuyParsnipButton { BuySeed(Item.parsnip_seeds, 5, 10) }
|
|
3538
|
+
if clicked == Scene.BuyPotatoButton { BuySeed(Item.potato_seeds, 5, 20) }
|
|
3539
|
+
if clicked == Scene.BuyBeanButton { BuySeed(Item.bean_seeds, 5, 30) }
|
|
3540
|
+
if clicked == Scene.CloseShopButton { ShopPanel.SetVisible(false); ShopMessage.SetText("") }
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
event KeyboardDownByThisPlayer[F] {
|
|
3544
|
+
if SelectedTool() == Item.fishing_rod {
|
|
3545
|
+
UsedFishing = true
|
|
3546
|
+
SpawnFloatingText("Fishing coming soon", ItemTint(Item.fishing_rod))
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
event KeyboardDownByThisPlayer[E] {
|
|
3551
|
+
if Sleeping { return }
|
|
3552
|
+
var bed = NearestObjectWithTag(Tag.Bed)
|
|
3553
|
+
if bed.Exists() and SurfaceGapXYTo(bed) < 140 {
|
|
3554
|
+
bed.ClearSay()
|
|
3555
|
+
Sleeping = true
|
|
3556
|
+
SleepMoveAllowed = false
|
|
3557
|
+
SleepFade.SetVisible(true)
|
|
3558
|
+
DayToast.SetVisible(false)
|
|
3559
|
+
for i in 0..11 {
|
|
3560
|
+
SleepFade.SetUiOpacity(i / 10)
|
|
3561
|
+
Wait(0.1)
|
|
3562
|
+
}
|
|
3563
|
+
Day += 1
|
|
3564
|
+
Energy = MaxEnergy
|
|
3565
|
+
for plot in GetAllObjectsWithTag(Tag.Plot) {
|
|
3566
|
+
plot.NewDay()
|
|
3567
|
+
}
|
|
3568
|
+
RefreshHud()
|
|
3569
|
+
DayToast.SetText("Day " + Day)
|
|
3570
|
+
DayToast.SetVisible(true)
|
|
3571
|
+
SleepMoveAllowed = true
|
|
3572
|
+
for j in 0..11 {
|
|
3573
|
+
SleepFade.SetUiOpacity(1 - j / 10)
|
|
3574
|
+
Wait(0.1)
|
|
3575
|
+
}
|
|
3576
|
+
SleepFade.SetVisible(false)
|
|
3577
|
+
Wait(1.0)
|
|
3578
|
+
DayToast.SetVisible(false)
|
|
3579
|
+
SleepMoveAllowed = false
|
|
3580
|
+
Sleeping = false
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
#end
|
|
3584
|
+
|
|
3585
|
+
#class FarmPlot
|
|
3586
|
+
var Tilled = false
|
|
3587
|
+
var CropStage = 0
|
|
3588
|
+
var CropAge = 0
|
|
3589
|
+
var CropDef: Object
|
|
3590
|
+
var Watered = false
|
|
3591
|
+
var CropVisual: Object
|
|
3592
|
+
|
|
3593
|
+
func CropColor(stage: Number) -> Vector {
|
|
3594
|
+
if CropDef == Item.potato {
|
|
3595
|
+
if stage == 1 { return Vector(0.25, 0.16, 0.06) }
|
|
3596
|
+
if stage == 2 { return Vector(0.55, 0.38, 0.18) }
|
|
3597
|
+
if stage == 3 { return Vector(0.70, 0.50, 0.22) }
|
|
3598
|
+
return Vector(0.82, 0.60, 0.25)
|
|
3599
|
+
}
|
|
3600
|
+
if CropDef == Item.bean {
|
|
3601
|
+
if stage == 1 { return Vector(0.05, 0.22, 0.08) }
|
|
3602
|
+
if stage == 2 { return Vector(0.10, 0.55, 0.18) }
|
|
3603
|
+
if stage == 3 { return Vector(0.10, 0.78, 0.24) }
|
|
3604
|
+
return Vector(0.12, 0.95, 0.35)
|
|
3605
|
+
}
|
|
3606
|
+
if stage == 1 { return Vector(0.16, 0.09, 0.04) }
|
|
3607
|
+
if stage == 2 { return Vector(0.30, 0.70, 0.25) }
|
|
3608
|
+
if stage == 3 { return Vector(0.55, 0.85, 0.22) }
|
|
3609
|
+
return Vector(1.0, 0.85, 0.25)
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
func UpdateLook() {
|
|
3613
|
+
if not Tilled {
|
|
3614
|
+
SetColor(0.70, 0.90, 0.70)
|
|
3615
|
+
} else {
|
|
3616
|
+
if Watered { SetColor(0.55, 0.50, 0.45) } else { SetColor(1, 1, 1) }
|
|
3617
|
+
}
|
|
3618
|
+
SetSizeTiles(0.9, 0.9, 0.02)
|
|
3619
|
+
|
|
3620
|
+
if CropStage == 0 {
|
|
3621
|
+
if CropVisual.Exists() { CropVisual.Destroy() }
|
|
3622
|
+
} else {
|
|
3623
|
+
if not CropVisual.Exists() { CropVisual = SpawnObject(SavedObject.cropVisual, GetPosition().Up(8)) }
|
|
3624
|
+
CropVisual.SetPosition(GetPosition().Up(8))
|
|
3625
|
+
if CropDef == Item.potato { CropVisual.SetModel(Ori.alias.model.carrot) }
|
|
3626
|
+
else if CropDef == Item.bean { CropVisual.SetModel(Ori.alias.model.grape) }
|
|
3627
|
+
else { CropVisual.SetModel(Ori.alias.model.cabbage) }
|
|
3628
|
+
if CropStage == 1 {
|
|
3629
|
+
CropVisual.SetColor(CropColor(1))
|
|
3630
|
+
CropVisual.SetSize(28, 28, 10)
|
|
3631
|
+
} else if CropStage == 2 {
|
|
3632
|
+
CropVisual.SetColor(CropColor(2))
|
|
3633
|
+
CropVisual.SetSize(55, 55, 25)
|
|
3634
|
+
} else if CropStage == 3 {
|
|
3635
|
+
CropVisual.SetColor(CropColor(3))
|
|
3636
|
+
CropVisual.SetSize(65, 65, 45)
|
|
3637
|
+
} else {
|
|
3638
|
+
CropVisual.SetColor(CropColor(4))
|
|
3639
|
+
CropVisual.SetSize(75, 75, 65)
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
event OnSpawned {
|
|
3645
|
+
AddTag(Tag.Plot)
|
|
3646
|
+
UpdateLook()
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
func NewDay() {
|
|
3650
|
+
if CropStage > 0 and CropStage < 4 {
|
|
3651
|
+
if Watered {
|
|
3652
|
+
CropAge += 1
|
|
3653
|
+
CropStage = 1 + CropAge
|
|
3654
|
+
if CropStage > 4 { CropStage = 4 }
|
|
3655
|
+
}
|
|
3656
|
+
Watered = false
|
|
3657
|
+
UpdateLook()
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
func Harvest(p: Object) {
|
|
3662
|
+
if p.FarmAddItem(CropDef, 1) {
|
|
3663
|
+
if p.SelectedTool() == Item.scythe { p.UsedScythe = true }
|
|
3664
|
+
p.HasHarvested = true
|
|
3665
|
+
var harvested = CropDef
|
|
3666
|
+
CropStage = 0
|
|
3667
|
+
CropAge = 0
|
|
3668
|
+
Watered = false
|
|
3669
|
+
if p.SelectedTool() == Item.scythe { p.SpawnFloatingText("Scythed " + ItemName(harvested), Vector(1, 1, 0.2)) }
|
|
3670
|
+
else { p.SpawnFloatingText("Harvested " + ItemName(harvested), Vector(1, 1, 0.2)) }
|
|
3671
|
+
UpdateLook()
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
func ApplyTool(p: Object) {
|
|
3676
|
+
if SurfaceGapXYTo(p) > 120 {
|
|
3677
|
+
p.SpawnFloatingText("Too far", Vector(1, 1, 0))
|
|
3678
|
+
return
|
|
3679
|
+
}
|
|
3680
|
+
if CropStage == 4 {
|
|
3681
|
+
Harvest(p)
|
|
3682
|
+
return
|
|
3683
|
+
}
|
|
3684
|
+
var sel = p.SelectedTool()
|
|
3685
|
+
if sel == Item.hoe {
|
|
3686
|
+
if not Tilled {
|
|
3687
|
+
if p.Energy <= 0 { p.SpawnFloatingText("Too tired", Vector(1, 0, 0)); return }
|
|
3688
|
+
p.UsedHoe = true
|
|
3689
|
+
Tilled = true
|
|
3690
|
+
p.Energy -= 3
|
|
3691
|
+
p.SpawnFloatingText("Tilled soil", Vector(0.7, 0.4, 0.1))
|
|
3692
|
+
p.RefreshHud()
|
|
3693
|
+
UpdateLook()
|
|
3694
|
+
} else {
|
|
3695
|
+
p.SpawnFloatingText("Already tilled", Vector(1, 1, 1))
|
|
3696
|
+
}
|
|
3697
|
+
} else if sel == Item.parsnip_seeds or sel == Item.potato_seeds or sel == Item.bean_seeds {
|
|
3698
|
+
if Tilled and CropStage == 0 {
|
|
3699
|
+
CropDef = Item.parsnip
|
|
3700
|
+
if sel == Item.potato_seeds { CropDef = Item.potato }
|
|
3701
|
+
if sel == Item.bean_seeds { CropDef = Item.bean }
|
|
3702
|
+
if p.ConsumeSelectedOne() {
|
|
3703
|
+
p.UsedSeeds = true
|
|
3704
|
+
CropStage = 1
|
|
3705
|
+
CropAge = 0
|
|
3706
|
+
p.SpawnFloatingText("Planted seeds", Vector(0.2, 1, 0.2))
|
|
3707
|
+
UpdateLook()
|
|
3708
|
+
}
|
|
3709
|
+
} else {
|
|
3710
|
+
p.SpawnFloatingText("Seeds need empty tilled soil", Vector(1, 1, 0))
|
|
3711
|
+
}
|
|
3712
|
+
} else if sel == Item.watering_can {
|
|
3713
|
+
if p.Water <= 0 {
|
|
3714
|
+
p.SpawnFloatingText("Watering can empty. Refill at pond.", Vector(0.2, 0.6, 1))
|
|
3715
|
+
p.RefreshHud()
|
|
3716
|
+
return
|
|
3717
|
+
}
|
|
3718
|
+
if Tilled and CropStage < 4 {
|
|
3719
|
+
p.UsedWater = true
|
|
3720
|
+
Watered = true
|
|
3721
|
+
p.Water -= 1
|
|
3722
|
+
p.Energy -= 1
|
|
3723
|
+
p.SpawnFloatingText("Watered", Vector(0.2, 0.6, 1))
|
|
3724
|
+
p.RefreshHud()
|
|
3725
|
+
UpdateLook()
|
|
3726
|
+
} else {
|
|
3727
|
+
p.SpawnFloatingText("Nothing to water", Vector(1, 1, 1))
|
|
3728
|
+
}
|
|
3729
|
+
} else if sel == Item.scythe {
|
|
3730
|
+
p.SpawnFloatingText("Nothing mature here", Vector(1, 1, 1))
|
|
3731
|
+
} else {
|
|
3732
|
+
p.SpawnFloatingText("Equip hoe, seeds, watering can, or scythe", Vector(1, 1, 1))
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
#end
|
|
3736
|
+
|
|
3737
|
+
#class ShippingBin
|
|
3738
|
+
event OnSpawned {
|
|
3739
|
+
AddTag(Tag.SellBox)
|
|
3740
|
+
SetColor(0.45, 0.25, 0.05)
|
|
3741
|
+
Say("Shipping Bin\nDrag crops here to sell")
|
|
3742
|
+
}
|
|
3743
|
+
#end
|
|
3744
|
+
|
|
3745
|
+
#class ShopCounter
|
|
3746
|
+
event ClickThisObject {
|
|
3747
|
+
var p = Event.Presser
|
|
3748
|
+
p.ShopPanel.SetVisible(true)
|
|
3749
|
+
p.ShopMessage.SetText("")
|
|
3750
|
+
}
|
|
3751
|
+
#end
|
|
3752
|
+
|
|
3753
|
+
#class ToolResource
|
|
3754
|
+
var HP = 3
|
|
3755
|
+
var IsTree = true // true: axe chops it for wood; false: pickaxe breaks it for stone
|
|
3756
|
+
var DropCount = 1
|
|
3757
|
+
var Label = "Resource"
|
|
3758
|
+
|
|
3759
|
+
event OnSpawned {
|
|
3760
|
+
AddTag(Tag.Resource)
|
|
3761
|
+
Say(Label)
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
func ApplyTool(p: Object) {
|
|
3765
|
+
var needed = Item.pickaxe
|
|
3766
|
+
if IsTree { needed = Item.axe }
|
|
3767
|
+
if p.SelectedTool() != needed {
|
|
3768
|
+
p.SpawnFloatingText("Use " + ItemName(needed), Vector(1, 1, 1))
|
|
3769
|
+
return
|
|
3770
|
+
}
|
|
3771
|
+
if SurfaceGapXYTo(p) > 125 {
|
|
3772
|
+
p.SpawnFloatingText("Too far", Vector(1, 1, 0))
|
|
3773
|
+
return
|
|
3774
|
+
}
|
|
3775
|
+
HP -= 1
|
|
3776
|
+
if IsTree { p.UsedAxe = true } else { p.UsedPickaxe = true }
|
|
3777
|
+
p.Energy -= 2
|
|
3778
|
+
p.RefreshHud()
|
|
3779
|
+
SpawnFloatingText("-1", Vector(1, 1, 1))
|
|
3780
|
+
if HP <= 0 {
|
|
3781
|
+
var drop = Item.stone
|
|
3782
|
+
if IsTree { drop = Item.wood }
|
|
3783
|
+
if p.FarmAddItem(drop, DropCount) {
|
|
3784
|
+
p.ShowPickupItem(drop)
|
|
3785
|
+
} else {
|
|
3786
|
+
p.DropItemInFront(drop, DropCount)
|
|
3787
|
+
}
|
|
3788
|
+
Destroy()
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
event ClickThisObject {
|
|
3793
|
+
ApplyTool(Event.Presser)
|
|
3794
|
+
}
|
|
3795
|
+
#end
|
|
3796
|
+
|
|
3797
|
+
#class GroundItem
|
|
3798
|
+
var ItemDef: Object
|
|
3799
|
+
var Count = 1
|
|
3800
|
+
|
|
3801
|
+
func RefreshDrop() {
|
|
3802
|
+
if ItemDef == Item.parsnip { SetModel(Ori.alias.model.cabbage) }
|
|
3803
|
+
else if ItemDef == Item.wood { SetModel(Ori.alias.model.trunk) }
|
|
3804
|
+
else if ItemDef == Item.stone { SetModel(Ori.alias.model.flat_rock_slab) }
|
|
3805
|
+
else if ItemDef == Item.potato { SetModel(Ori.alias.model.carrot) }
|
|
3806
|
+
else if ItemDef == Item.bean { SetModel(Ori.alias.model.grape) }
|
|
3807
|
+
SetColor(ItemTint(ItemDef))
|
|
3808
|
+
Say(ItemName(ItemDef) + " x" + Count + "\nClick to pick up")
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
event OnSpawned {
|
|
3812
|
+
AddTag(Tag.Pickup)
|
|
3813
|
+
SetSizeTiles(0.35, 0.35, 0.35)
|
|
3814
|
+
RefreshDrop()
|
|
3815
|
+
var startPos = GetPosition()
|
|
3816
|
+
var restPos = Position(startPos.X, startPos.Y, 9)
|
|
3817
|
+
forever {
|
|
3818
|
+
if GetPosition().DistanceTo(restPos) >= 2 { MoveTowardContinuous(restPos, 260) }
|
|
3819
|
+
var p = NearestPlayer()
|
|
3820
|
+
if p.Exists() and SurfaceGapXYTo(p) < 160 {
|
|
3821
|
+
Say(ItemName(ItemDef) + " x" + Count + "\nClick to pick up")
|
|
3822
|
+
} else {
|
|
3823
|
+
ClearSay()
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
event ClickThisObject {
|
|
3829
|
+
var p = Event.Presser
|
|
3830
|
+
if p.FarmAddItem(ItemDef, Count) {
|
|
3831
|
+
p.SpawnFloatingText("Picked up " + ItemName(ItemDef), ItemTint(ItemDef))
|
|
3832
|
+
Destroy()
|
|
3833
|
+
} else {
|
|
3834
|
+
SpawnFloatingText("Inventory full", Vector(1, 0, 0))
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
#end
|
|
3838
|
+
|
|
3839
|
+
#class StardewRoomBuilder
|
|
3840
|
+
event OnSpawned {
|
|
3841
|
+
var farmhouseWalls = BuildRoom(Position(-400, 400, 0), Vector(400, 300, 0), 20, 150, Vector(0, -1, 0), 400)
|
|
3842
|
+
for wall in farmhouseWalls {
|
|
3843
|
+
wall.SetColor(Vector(0.65, 0.35, 0.2))
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
var shopWalls = BuildRoom(Position(500, 600, 0), Vector(500, 400, 0), 18, 150, Vector(0, -1, 0), 150)
|
|
3847
|
+
for wall in shopWalls {
|
|
3848
|
+
wall.SetColor(Vector(0.55, 0.30, 0.16))
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
#end
|
|
3852
|
+
|
|
3853
|
+
material GroundMat { base = "StylizedGrass" }
|
|
3854
|
+
object ground {
|
|
3855
|
+
model = "cube"
|
|
3856
|
+
material = "GroundMat"
|
|
3857
|
+
position = (0, 0, -100)
|
|
3858
|
+
size = (10000, 10000, 100)
|
|
3859
|
+
}
|
|
3860
|
+
display ___StardewRoomBuilder___ : StardewRoomBuilder { visible = false }
|
|
3861
|
+
|
|
3862
|
+
// farm plots
|
|
3863
|
+
display plot_1_1 : FarmPlot { model="alias.model.soil_tile" position=(-100, 0, 1) }
|
|
3864
|
+
display plot_1_2 : FarmPlot { model="alias.model.soil_tile" position=(0, 0, 1) }
|
|
3865
|
+
display plot_1_3 : FarmPlot { model="alias.model.soil_tile" position=(100, 0, 1) }
|
|
3866
|
+
display plot_2_1 : FarmPlot { model="alias.model.soil_tile" position=(-100, 100, 1) }
|
|
3867
|
+
display plot_2_2 : FarmPlot { model="alias.model.soil_tile" position=(0, 100, 1) }
|
|
3868
|
+
display plot_2_3 : FarmPlot { model="alias.model.soil_tile" position=(100, 100, 1) }
|
|
3869
|
+
display plot_3_1 : FarmPlot { model="alias.model.soil_tile" position=(-100, 200, 1) }
|
|
3870
|
+
display plot_3_2 : FarmPlot { model="alias.model.soil_tile" position=(0, 200, 1) }
|
|
3871
|
+
display plot_3_3 : FarmPlot { model="alias.model.soil_tile" position=(100, 200, 1) }
|
|
3872
|
+
|
|
3873
|
+
object shipBin : ShippingBin {
|
|
3874
|
+
model="alias.model.chest"
|
|
3875
|
+
position_tiles=(3, 0)
|
|
3876
|
+
size_tiles=(0.8, 0.8, 0.8)
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
object pond {
|
|
3880
|
+
model="alias.model.pond"
|
|
3881
|
+
position_tiles=(4, 3)
|
|
3882
|
+
size_tiles=(2, 1.5, 0.04)
|
|
3883
|
+
color=(0.1, 0.35, 0.8)
|
|
3884
|
+
tag=Tag.Pond
|
|
3885
|
+
}
|
|
3886
|
+
object farmhouseFloor {
|
|
3887
|
+
model="alias.model.farmhouse_floor"
|
|
3888
|
+
position_tiles=(-4, 4)
|
|
3889
|
+
size_tiles=(4, 3, 0.08)
|
|
3890
|
+
color=(0.45, 0.28, 0.16)
|
|
3891
|
+
}
|
|
3892
|
+
object bed {
|
|
3893
|
+
model="alias.model.bed"
|
|
3894
|
+
position_tiles=(-5, 4.6)
|
|
3895
|
+
size=(112, 80, 64)
|
|
3896
|
+
color=(0.2, 0.35, 0.9)
|
|
3897
|
+
tag=Tag.Bed
|
|
3898
|
+
}
|
|
3899
|
+
object treeA : ToolResource { model="alias.model.green_maple_tree" position_tiles=(-5, -2) height=500 color=(0.25,0.15,0.05) override IsTree=true override DropCount=3 override Label="Tree" }
|
|
3900
|
+
object treeB : ToolResource { model="alias.model.green_maple_tree" position_tiles=(5, -2) height=500 color=(0.25,0.15,0.05) override IsTree=true override DropCount=3 override Label="Tree" }
|
|
3901
|
+
object rockA : ToolResource { model="alias.model.flat_rock_slab" position_tiles=(-3, -2) size_tiles=(0.7,0.7,0.45) color=(0.45,0.45,0.45) override IsTree=false override DropCount=3 override Label="Rock" }
|
|
3902
|
+
object rockB : ToolResource { model="alias.model.grass_rock_cliff" position_tiles=(3, -2) size_tiles=(0.7,0.7,0.45) color=(0.45,0.45,0.45) override IsTree=false override DropCount=3 override Label="Rock" }
|
|
3903
|
+
|
|
3904
|
+
object shopFloor { model="alias.model.shop_floor" position_tiles=(5, 6) size_tiles=(5, 4, 0.08) color=(0.42, 0.30, 0.18) }
|
|
3905
|
+
object shopCounter : ShopCounter {
|
|
3906
|
+
model="alias.model.shop_counter"
|
|
3907
|
+
position=(563, 658, 73)
|
|
3908
|
+
rotation=(0, 0, -90)
|
|
3909
|
+
size=(80, 215, 130)
|
|
3910
|
+
tag=Tag.ShopCounter
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
saved groundItem {
|
|
3914
|
+
object drop : GroundItem { model="alias.model.ground_item" size_tiles=(0.35, 0.35, 0.35) }
|
|
3915
|
+
}
|
|
3916
|
+
saved heldItemVisual {
|
|
3917
|
+
display heldItem { model="alias.model.held_item" size=30 visible=false }
|
|
3918
|
+
}
|
|
3919
|
+
saved cropVisual {
|
|
3920
|
+
display crop { model="alias.model.crop" size=(28,28,10) }
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
screenCanvas FarmUI {
|
|
3924
|
+
image GoldIcon {
|
|
3925
|
+
offset = (20, 18)
|
|
3926
|
+
size = (30, 30)
|
|
3927
|
+
}
|
|
3928
|
+
image EnergyIcon {
|
|
3929
|
+
offset = (20, 51)
|
|
3930
|
+
size = (30, 30)
|
|
3931
|
+
}
|
|
3932
|
+
textLabel DayText {
|
|
3933
|
+
offset = (20, 96)
|
|
3934
|
+
text = "Day 1"
|
|
3935
|
+
color = (1, 1, 0.85)
|
|
3936
|
+
}
|
|
3937
|
+
// vStack auto-stacks the rows (centered); no size = hug to fit children. Center-anchored modal.
|
|
3938
|
+
vStack ShopPanel {
|
|
3939
|
+
visible = false
|
|
3940
|
+
anchor = (center, center)
|
|
3941
|
+
padding = 20
|
|
3942
|
+
gap = 12
|
|
3943
|
+
child_align_x = center
|
|
3944
|
+
color = #14100AF2
|
|
3945
|
+
corner_radius = 14
|
|
3946
|
+
border_width = 1
|
|
3947
|
+
border_color = #7A5A2EFF
|
|
3948
|
+
textLabel ShopTitle { size=(320, 30) align=(center, center) text="Seed Shop" color=(1, 1, 0.8) }
|
|
3949
|
+
button BuyParsnipButton { size=(300, 48) text="5 Parsnip Seeds - 10g" corner_radius=8 }
|
|
3950
|
+
button BuyPotatoButton { size=(300, 48) text="5 Potato Seeds - 20g" corner_radius=8 }
|
|
3951
|
+
button BuyBeanButton { size=(300, 48) text="5 Bean Seeds - 30g" corner_radius=8 }
|
|
3952
|
+
textLabel ShopMessage { size=(320, 24) align=(center, center) text="" color=(1, 0.1, 0.1) }
|
|
3953
|
+
button CloseShopButton { size=(140, 38) text="Close" corner_radius=8 }
|
|
3954
|
+
}
|
|
3955
|
+
// The whole hotbar: ONE empty hStack. BindInventory in player OnSpawned spawns a synced
|
|
3956
|
+
// draggable 88x88 slot (icon + count + selection highlight) per inventory slot.
|
|
3957
|
+
hStack Hotbar {
|
|
3958
|
+
anchor = (center, bottom)
|
|
3959
|
+
offset = (0, -6)
|
|
3960
|
+
color = (0.12, 0.08, 0.04)
|
|
3961
|
+
padding = 8
|
|
3962
|
+
gap = 4
|
|
3963
|
+
}
|
|
3964
|
+
textLabel GoldText {
|
|
3965
|
+
offset = (58, 22)
|
|
3966
|
+
text = "0"
|
|
3967
|
+
color = (1, 1, 1)
|
|
3968
|
+
}
|
|
3969
|
+
textLabel EnergyText {
|
|
3970
|
+
offset = (58, 55)
|
|
3971
|
+
text = "100/100"
|
|
3972
|
+
color = (1, 1, 1)
|
|
3973
|
+
}
|
|
3974
|
+
frame SleepFade {
|
|
3975
|
+
visible = false
|
|
3976
|
+
offset = (0, 0)
|
|
3977
|
+
size = (1600, 900)
|
|
3978
|
+
color = (0, 0, 0)
|
|
3979
|
+
}
|
|
3980
|
+
textLabel DayToast {
|
|
3981
|
+
visible = false
|
|
3982
|
+
offset = (725, 410)
|
|
3983
|
+
text = "Day 1"
|
|
3984
|
+
color = (1, 1, 1)
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
screenCanvas TutorialUI {
|
|
3989
|
+
textLabel HelpText {
|
|
3990
|
+
offset = (20, 130)
|
|
3991
|
+
text = """Move with WASD or mobile thumbstick
|
|
3992
|
+
Tap hotbar slots to equip
|
|
3993
|
+
Primary action uses the selected item
|
|
3994
|
+
Drag items out of slots to sell or drop them"""
|
|
3995
|
+
color = (1, 1, 1)
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
</example_stardew_valley>
|
|
4000
|
+
|
|
4001
|
+
|
|
4002
|
+
<example_texture_dsl_desert_forest_demo>
|
|
4003
|
+
// Procedural texture DSL demo: semi-realistic desert wall + forest ground.
|
|
4004
|
+
|
|
4005
|
+
#texture DesertWall
|
|
4006
|
+
tile_m = 2.0;
|
|
4007
|
+
let tile = pattern.tile(uv * vec2(6.0, 4.0), mortar: 0.065, seed: 3);
|
|
4008
|
+
let warp = noise.vec2(uv * 3.0, seed: 11, period: 3) * 0.18;
|
|
4009
|
+
let cells = pattern.voronoi(uv * vec2(12.0, 8.0) + warp, jitter: 0.82, seed: 7, period: vec2(12.0, 8.0));
|
|
4010
|
+
let cracks = stroke(cells.edge, width: 0.055, aa: 0.012);
|
|
4011
|
+
let grain = noise.ridged(uv * 72.0, octaves: 3, seed: 19, period: 72);
|
|
4012
|
+
let broad = noise.fbm(uv * vec2(6.0, 4.0), octaves: 4, seed: 23, period: vec2(6.0, 4.0));
|
|
4013
|
+
let chips = smoothstep(0.65, 0.95, noise.ridged(uv * 36.0, octaves: 3, seed: 29, period: 36)) * (1.0 - smoothstep(0.02, 0.18, tile.edge));
|
|
4014
|
+
let dust = levels(noise.fbm(uv * 5.0 + warp, octaves: 4, seed: 31, period: 5), black: 0.28, white: 0.86, gamma: 1.35);
|
|
4015
|
+
let block = contrast(sat(broad * 0.65 + grain * 0.25 + rand(tile.id, seed: 5) * 0.10), amount: 1.25, pivot: 0.45);
|
|
4016
|
+
let height = mix(-0.045, 0.11 + grain * 0.018 - chips * 0.06, tile.mask) - cracks * 0.07;
|
|
4017
|
+
let stone = ramp(block, [0.0: color("#7d664a"), 0.35: color("#a8875a"), 0.75: color("#c9a66f"), 1.0: color("#ead19c")]);
|
|
4018
|
+
let mortar = color("#75614b") * mix(0.82, 1.08, broad);
|
|
4019
|
+
out.color = mix(mortar, stone, tile.mask) * mix(0.82, 1.06, dust) - cracks * 0.12;
|
|
4020
|
+
out.normal = normal_from_height(height, strength: 1.05);
|
|
4021
|
+
out.orm = orm(occlusion: ao_from_height(height, radius: 2.5), roughness: mix(0.94, 0.82, tile.mask) + dust * 0.03, metallic: 0.0);
|
|
4022
|
+
#end
|
|
4023
|
+
|
|
4024
|
+
#texture ForestGround
|
|
4025
|
+
tile_m = 3.0;
|
|
4026
|
+
let p = uv * vec2(8.0, 8.0);
|
|
4027
|
+
let warp = noise.vec2(uv * 2.0, seed: 41, period: 2) * 0.25;
|
|
4028
|
+
let soil = levels(noise.fbm(p + warp, octaves: 4, seed: 43, period: vec2(8.0, 8.0)), black: 0.18, white: 0.88, gamma: 1.2);
|
|
4029
|
+
let stones = pattern.voronoi(uv * vec2(7.0, 7.0) + warp * 0.6, jitter: 0.78, seed: 47, period: vec2(7.0, 7.0));
|
|
4030
|
+
let stone_shape = 1.0 - smoothstep(0.34, 0.47, stones.dist + noise.fbm(stones.local * 4.0, octaves: 2, seed: 53) * 0.08);
|
|
4031
|
+
let cell_guard = smoothstep(0.025, 0.07, stones.edge);
|
|
4032
|
+
let stone_mask = stone_shape * cell_guard;
|
|
4033
|
+
let moss = levels(noise.fbm(uv * vec2(5.0, 5.0) + warp * 1.4, octaves: 4, seed: 59, period: 5) + (1.0 - stone_mask) * 0.25, black: 0.44, white: 0.92, gamma: 0.85);
|
|
4034
|
+
let wet = contrast(noise.fbm(uv * 3.0 + warp, octaves: 3, seed: 61, period: 3), amount: 1.55, pivot: 0.48);
|
|
4035
|
+
let height = soil * 0.045 + moss * 0.035 + sat(1.0 - stones.dist * 2.1) * stone_mask * 0.10 - (1.0 - soil) * 0.025;
|
|
4036
|
+
let soil_color = ramp(soil, [0.0: color("#211912"), 0.45: color("#3b2b1d"), 1.0: color("#6c5538")]);
|
|
4037
|
+
let moss_color = ramp(moss, [0.0: color("#26351d"), 0.55: color("#43622d"), 1.0: color("#7f9b55")]);
|
|
4038
|
+
let stone_color = ramp(rand(stones.id, seed: 67), [0.0: color("#3e4039"), 0.5: color("#68675b"), 1.0: color("#918a78")]);
|
|
4039
|
+
out.color = mix(mix(soil_color, moss_color, moss * 0.82), stone_color, stone_mask * 0.75);
|
|
4040
|
+
out.normal = normal_from_height(height, strength: 0.95);
|
|
4041
|
+
out.orm = orm(occlusion: ao_from_height(height, radius: 2.0), roughness: mix(0.92, 0.58, wet), metallic: 0.0);
|
|
4042
|
+
#end
|
|
4043
|
+
|
|
4044
|
+
object desert_wall {
|
|
4045
|
+
model = "cube"
|
|
4046
|
+
material = "DesertWall"
|
|
4047
|
+
position = (0, -240, 160)
|
|
4048
|
+
size = (700, 40, 320)
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
object forest_ground {
|
|
4052
|
+
model = "cube"
|
|
4053
|
+
material = "ForestGround"
|
|
4054
|
+
position = (0, 180, -20)
|
|
4055
|
+
size = (900, 520, 40)
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
</example_texture_dsl_desert_forest_demo>
|
|
4059
|
+
|
|
4060
|
+
|
|
4061
|
+
<example_texture_dsl_grunge_demo>
|
|
4062
|
+
// Procedural texture DSL demo: scalar mask morphology for grunge and worn painted metal.
|
|
4063
|
+
|
|
4064
|
+
#texture GrungeMask
|
|
4065
|
+
tile_m = 1.5;
|
|
4066
|
+
wrap = repeat;
|
|
4067
|
+
let p = uv * vec2(8.0, 8.0);
|
|
4068
|
+
let warp = noise.vec2(uv * vec2(3.0, 3.0), seed: 71, period: 3) * 0.35;
|
|
4069
|
+
let cloudy = levels(noise.fbm(p + warp, octaves: 5, seed: 73, period: vec2(8.0, 8.0)), black: 0.24, white: 0.82, gamma: 1.18);
|
|
4070
|
+
let pepper = levels(noise.ridged(uv * vec2(72.0, 72.0), octaves: 3, seed: 79, period: 72), black: 0.58, white: 0.94, gamma: 0.75);
|
|
4071
|
+
let streak = levels(noise.ridged(vec2(uv.x * 160.0, uv.y * 5.0), octaves: 2, seed: 83, period: vec2(160.0, 5.0)), black: 0.66, white: 0.93, gamma: 0.65);
|
|
4072
|
+
let pits = dilate(erode(sat(cloudy * 0.62 + pepper * 0.32 + streak * 0.22), radius: 1.0), radius: 1.5);
|
|
4073
|
+
let softened = blur(pits, radius: 1.25);
|
|
4074
|
+
out.color = vec3(softened, softened, softened);
|
|
4075
|
+
out.normal = normal_from_height(softened * 0.05, strength: 0.45);
|
|
4076
|
+
out.orm = orm(occlusion: 1.0 - softened * 0.18, roughness: mix(0.55, 0.95, softened), metallic: 0.0);
|
|
4077
|
+
#end
|
|
4078
|
+
|
|
4079
|
+
#texture WornPaintedMetal
|
|
4080
|
+
tile_m = 1.0;
|
|
4081
|
+
wrap = repeat;
|
|
4082
|
+
let p = uv * vec2(10.0, 10.0);
|
|
4083
|
+
let warp = noise.vec2(uv * vec2(4.0, 4.0), seed: 89, period: 4) * 0.28;
|
|
4084
|
+
let broad = noise.fbm(uv * vec2(5.0, 5.0) + warp, octaves: 4, seed: 97, period: 5);
|
|
4085
|
+
let cells = pattern.voronoi(uv * vec2(14.0, 14.0) + warp * 0.5, jitter: 0.74, seed: 101, period: 14);
|
|
4086
|
+
let crack = stroke(cells.edge, width: 0.035, aa: 0.010);
|
|
4087
|
+
let scratch = levels(noise.ridged(vec2(uv.x * 180.0, uv.y * 7.0), octaves: 2, seed: 103, period: vec2(180.0, 7.0)), black: 0.68, white: 0.96, gamma: 0.72);
|
|
4088
|
+
let chip_seed = levels(noise.ridged(p + warp, octaves: 4, seed: 107, period: 10), black: 0.62, white: 0.92, gamma: 0.85);
|
|
4089
|
+
let raw_wear = sat(chip_seed * 0.70 + scratch * 0.32 + crack * 0.55 - broad * 0.18);
|
|
4090
|
+
let wear = blur(dilate(erode(raw_wear, radius: 0.75), radius: 2.0), radius: 0.75);
|
|
4091
|
+
let rust = levels(noise.fbm(uv * vec2(18.0, 18.0) + warp, octaves: 3, seed: 109, period: 18), black: 0.30, white: 0.90, gamma: 1.35);
|
|
4092
|
+
let paint = color("#275f6f") * mix(0.70, 1.15, broad);
|
|
4093
|
+
let exposed = mix(color("#6f7473"), color("#8c4a24"), rust * 0.55);
|
|
4094
|
+
let height = broad * 0.035 - wear * 0.070 + scratch * 0.018;
|
|
4095
|
+
out.color = mix(paint, exposed, wear);
|
|
4096
|
+
out.normal = normal_from_height(height, strength: 0.95);
|
|
4097
|
+
out.orm = orm(occlusion: ao_from_height(height, radius: 2.0), roughness: mix(0.62, 0.92, wear), metallic: mix(0.02, 0.70, wear * (1.0 - rust * 0.55)));
|
|
4098
|
+
#end
|
|
4099
|
+
|
|
4100
|
+
object grunge_panel {
|
|
4101
|
+
model = "cube"
|
|
4102
|
+
material = "GrungeMask"
|
|
4103
|
+
position = (-260, 0, 80)
|
|
4104
|
+
size = (260, 40, 180)
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
object worn_painted_metal_panel {
|
|
4108
|
+
model = "cube"
|
|
4109
|
+
material = "WornPaintedMetal"
|
|
4110
|
+
position = (180, 0, 80)
|
|
4111
|
+
size = (360, 40, 220)
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
</example_texture_dsl_grunge_demo>
|
|
4115
|
+
|
|
4116
|
+
|
|
4117
|
+
<example_tree_species>
|
|
4118
|
+
// Curated #tree species gallery. Each recipe is a reference worth copying as a
|
|
4119
|
+
// starting point for that habit; keep the shape keys, adjust seed/size per scene.
|
|
4120
|
+
// Iterate look changes with preview_tree and watch the foliage_coverage stat.
|
|
4121
|
+
|
|
4122
|
+
// European beech: broad asymmetric dome from four overlapping crown lobes, a
|
|
4123
|
+
// trunk that persists partway into the crown (leader) then dissolves into forks,
|
|
4124
|
+
// slender bole, dense cluster cards.
|
|
4125
|
+
#tree Beech
|
|
4126
|
+
seed: 27
|
|
4127
|
+
mode: colonize
|
|
4128
|
+
leader: 62
|
|
4129
|
+
levels: 4
|
|
4130
|
+
children: 7
|
|
4131
|
+
segments: 7
|
|
4132
|
+
trunk_height: 1250
|
|
4133
|
+
trunk_radius: 34
|
|
4134
|
+
points: 1024
|
|
4135
|
+
length_pct: 76
|
|
4136
|
+
radius_pct: 46
|
|
4137
|
+
angle_deg: 46
|
|
4138
|
+
angle_jitter_deg: 20
|
|
4139
|
+
up_bias_pct: 26
|
|
4140
|
+
curve_jitter_pct: 16
|
|
4141
|
+
trunk_noise_pct: 4
|
|
4142
|
+
trunk_lean_deg: 3 // gentle bands: beeches curve, they don't lean hard
|
|
4143
|
+
trunk_sweep_pct: 16
|
|
4144
|
+
trunk_noise_scale_pct: 30 // wavelength as % of height: slow bends, not bark-beat raggedness
|
|
4145
|
+
leaves: 12
|
|
4146
|
+
leaf_size: 178
|
|
4147
|
+
droop_pct: 8
|
|
4148
|
+
root_flare_pct: 34
|
|
4149
|
+
crown_ellipsoid: 0, 0, 1020, 620, 590, 510
|
|
4150
|
+
crown_ellipsoid: -250, 80, 960, 400, 370, 360
|
|
4151
|
+
crown_ellipsoid: 240, -100, 1060, 420, 390, 400
|
|
4152
|
+
crown_ellipsoid: 40, 50, 1380, 380, 350, 300
|
|
4153
|
+
#end
|
|
4154
|
+
|
|
4155
|
+
// Grand open-grown oak: a dominant central trunk visible deep into the tiered
|
|
4156
|
+
// crown, three low limb masses ~120 degrees apart in plan (every viewing angle
|
|
4157
|
+
// shows a reaching limb - lobes on one axis collapse to a blob from the thin
|
|
4158
|
+
// side), offset upper lobes, real sky gaps. An irregular ellipsoid stack, not
|
|
4159
|
+
// one sphere. Open crowns like this read ~35-45% coverage; gaps are the character.
|
|
4160
|
+
#tree Oak
|
|
4161
|
+
seed: 5
|
|
4162
|
+
mode: colonize
|
|
4163
|
+
leader: 35
|
|
4164
|
+
levels: 4
|
|
4165
|
+
children: 6
|
|
4166
|
+
segments: 7
|
|
4167
|
+
trunk_height: 950
|
|
4168
|
+
trunk_radius: 44
|
|
4169
|
+
points: 1024
|
|
4170
|
+
length_pct: 80
|
|
4171
|
+
radius_pct: 52
|
|
4172
|
+
angle_deg: 55
|
|
4173
|
+
angle_jitter_deg: 28
|
|
4174
|
+
up_bias_pct: 22
|
|
4175
|
+
curve_jitter_pct: 26
|
|
4176
|
+
trunk_noise_pct: 7
|
|
4177
|
+
trunk_lean_deg: 5 // low bands: a real oak is never a plumb pole
|
|
4178
|
+
trunk_sweep_pct: 20 // one slow arc; seed picks C vs S
|
|
4179
|
+
trunk_gnarl_pct: 40 // lobed lower-bole silhouette for hero close-ups (display-only)
|
|
4180
|
+
leaves: 13
|
|
4181
|
+
leaf_size: 190
|
|
4182
|
+
droop_pct: 12
|
|
4183
|
+
root_flare_pct: 45
|
|
4184
|
+
crown_ellipsoid: 0, 0, 1120, 700, 660, 560
|
|
4185
|
+
crown_ellipsoid: 435, 115, 930, 480, 430, 340
|
|
4186
|
+
crown_ellipsoid: -320, 320, 900, 470, 420, 330
|
|
4187
|
+
crown_ellipsoid: -115, -435, 1000, 450, 410, 350
|
|
4188
|
+
crown_ellipsoid: -200, -190, 1500, 450, 410, 340
|
|
4189
|
+
crown_ellipsoid: 230, 180, 1620, 390, 360, 300
|
|
4190
|
+
#end
|
|
4191
|
+
|
|
4192
|
+
// Fir: the dense layered conifer - whorl rings of downswept shelf branches
|
|
4193
|
+
// (angle_deg > 90 + up_bias swoop) rendered as whole-branch frond ribbons,
|
|
4194
|
+
// clear bole below crown_base, trunk-only collider. crown_base ~120 instead
|
|
4195
|
+
// gives the ground-sweeping park look.
|
|
4196
|
+
#tree Fir
|
|
4197
|
+
seed: 11
|
|
4198
|
+
mode: whorl
|
|
4199
|
+
leaf_mode: frond
|
|
4200
|
+
levels: 2
|
|
4201
|
+
children: 12
|
|
4202
|
+
rings: 30
|
|
4203
|
+
fill: 10
|
|
4204
|
+
segments: 8
|
|
4205
|
+
trunk_height: 1400
|
|
4206
|
+
trunk_radius: 22
|
|
4207
|
+
trunk_noise_pct: 2
|
|
4208
|
+
trunk_lean_deg: 1 // conifer spires stand near-plumb; kill the default lean/sweep
|
|
4209
|
+
trunk_sweep_pct: 3
|
|
4210
|
+
curve_jitter_pct: 5
|
|
4211
|
+
angle_deg: 106
|
|
4212
|
+
up_bias_pct: 8
|
|
4213
|
+
length_pct: 18
|
|
4214
|
+
length_taper_pct: 70
|
|
4215
|
+
droop_pct: 34
|
|
4216
|
+
leaf_size: 200
|
|
4217
|
+
root_flare_pct: 25
|
|
4218
|
+
crown_base: 300
|
|
4219
|
+
collider_levels: 1
|
|
4220
|
+
#end
|
|
4221
|
+
|
|
4222
|
+
// Weeping willow: spiral limbs arch up (up_bias) then cascade (droop) carrying
|
|
4223
|
+
// gravity-hung strand curtains (leaf_mode weeping). Long falls come from
|
|
4224
|
+
// leaf_size (the curtain card side, cm) - ~300 reaches near the ground from a
|
|
4225
|
+
// stout mid-height fork.
|
|
4226
|
+
#tree Willow
|
|
4227
|
+
seed: 9
|
|
4228
|
+
mode: spiral
|
|
4229
|
+
leaf_mode: weeping
|
|
4230
|
+
levels: 3
|
|
4231
|
+
children: 10
|
|
4232
|
+
segments: 8
|
|
4233
|
+
trunk_height: 750
|
|
4234
|
+
trunk_radius: 46
|
|
4235
|
+
length_pct: 82
|
|
4236
|
+
radius_pct: 50
|
|
4237
|
+
angle_deg: 40
|
|
4238
|
+
angle_jitter_deg: 18
|
|
4239
|
+
up_bias_pct: 34
|
|
4240
|
+
curve_jitter_pct: 18
|
|
4241
|
+
trunk_noise_pct: 5
|
|
4242
|
+
trunk_lean_deg: 4
|
|
4243
|
+
trunk_sweep_pct: 25 // waterside willows arc; pairs with the cascading crown
|
|
4244
|
+
droop_pct: 85
|
|
4245
|
+
leaves: 20
|
|
4246
|
+
leaf_size: 300
|
|
4247
|
+
root_flare_pct: 48
|
|
4248
|
+
#end
|
|
4249
|
+
|
|
4250
|
+
// Umbrella-thorn acacia: crown_dome mushroom cap on a bare fanning scaffold. The
|
|
4251
|
+
// short trunk + high cap forces colonize to bridge with long limbs; the TWO bare
|
|
4252
|
+
// guide ellipsoids make them diverge early and fork again mid-way (a lone
|
|
4253
|
+
// overhead cap pulls one straight vertical reach instead - attraction averages
|
|
4254
|
+
// out). children raises the forks per node. leaf_base at the dome cut with a
|
|
4255
|
+
// short fade keeps every guide bare and the cap underside crisp and flat.
|
|
4256
|
+
#tree Acacia
|
|
4257
|
+
seed: 4
|
|
4258
|
+
mode: colonize
|
|
4259
|
+
children: 7
|
|
4260
|
+
trunk_height: 260
|
|
4261
|
+
trunk_radius: 36
|
|
4262
|
+
points: 1024
|
|
4263
|
+
length_pct: 80
|
|
4264
|
+
radius_pct: 60
|
|
4265
|
+
curve_jitter_pct: 18
|
|
4266
|
+
trunk_noise_pct: 6
|
|
4267
|
+
trunk_lean_deg: 8 // savanna wind-shaped bole; crown_follow keeps the cap on it
|
|
4268
|
+
leaves: 13
|
|
4269
|
+
leaf_size: 185
|
|
4270
|
+
leaf_base: 740
|
|
4271
|
+
leaf_base_fade: 60
|
|
4272
|
+
root_flare_pct: 35
|
|
4273
|
+
crown_ellipsoid: 0, 0, 520, 300, 300, 160
|
|
4274
|
+
crown_ellipsoid: 0, 0, 690, 460, 460, 90
|
|
4275
|
+
crown_dome: 0, 0, 750, 800, 800, 360
|
|
4276
|
+
#end
|
|
4277
|
+
|
|
4278
|
+
object Beech1 { model = "tree.Beech" position = (0, 0, 0) }
|
|
4279
|
+
object Oak1 { model = "tree.Oak" position = (2200, 0, 0) }
|
|
4280
|
+
object Fir1 { model = "tree.Fir" position = (4000, 0, 0) }
|
|
4281
|
+
object Willow1 { model = "tree.Willow" position = (5800, 0, 0) }
|
|
4282
|
+
object Acacia1 { model = "tree.Acacia" position = (8000, 0, 0) }
|
|
4283
|
+
|
|
4284
|
+
</example_tree_species>
|
|
4285
|
+
|
|
4286
|
+
|
|
4287
|
+
<example_vampire_survivor>
|
|
4288
|
+
// Vampire Survivor Lite
|
|
4289
|
+
// Player: movement, auto-attack, level-up choices
|
|
4290
|
+
// Multiplayer: co-op shared arena.
|
|
4291
|
+
// Enemies/gems target the nearest player; per-player auto-attack and leveling.
|
|
4292
|
+
// Caveat: the level-up panel (LevelUpUI) is a single shared canvas and gates all players' attacks while open.
|
|
4293
|
+
alias model.goblin = "ori.model.nattawut_rea_6383.goblina"
|
|
4294
|
+
alias model.fox = "ori.default.fox"
|
|
4295
|
+
alias model.raptor = "ori.default.raptor"
|
|
4296
|
+
alias model.dragon = "ori.default.dragon"
|
|
4297
|
+
alias model.exp_gem // bare: auto-binds a generated mesh from the library
|
|
4298
|
+
alias effect.fireball = "ori.effect.worapat_supa_4983.w_fireball_2821"
|
|
4299
|
+
alias effect.poisonball = "ori.effect.worapat_supa_4983.w_poisonball_6320"
|
|
4300
|
+
alias effect.magicball = "ori.effect.nuttametee_b_2523.w_magicbulle"
|
|
4301
|
+
alias effect.snowball = "ori.effect.worapat_supa_4983.w_snowball_7592"
|
|
4302
|
+
alias effect.fire_area = "ori.effect.worapat_supa_4983.w_fireaura"
|
|
4303
|
+
alias effect.snowball_burst = "ori.effect.akkarawut_sa_2573.w_snowballbu41"
|
|
4304
|
+
|
|
4305
|
+
#class player
|
|
4306
|
+
var HP = 500
|
|
4307
|
+
var MaxHP = 500
|
|
4308
|
+
var XP = 0
|
|
4309
|
+
var NextLevelXP = 5
|
|
4310
|
+
|
|
4311
|
+
var AttackCooldown = 1.0 // seconds
|
|
4312
|
+
var AttackDamage = 35 // base damage
|
|
4313
|
+
|
|
4314
|
+
var WeaponLevel = 1
|
|
4315
|
+
var LevelUpPanel: Object
|
|
4316
|
+
var Choices: List<Object>
|
|
4317
|
+
|
|
4318
|
+
event OnSpawned {
|
|
4319
|
+
SetCameraToTopDown()
|
|
4320
|
+
TeleportToTile(0, 0)
|
|
4321
|
+
|
|
4322
|
+
HP = MaxHP
|
|
4323
|
+
SetHoverHPBar(HP, MaxHP)
|
|
4324
|
+
LevelUpPanel = Scene.LevelUpPanel
|
|
4325
|
+
LevelUpPanel.SetVisible(false)
|
|
4326
|
+
Choices.Add(LevelUpPanel.FindChildUI("Choice1"))
|
|
4327
|
+
Choices.Add(LevelUpPanel.FindChildUI("Choice2"))
|
|
4328
|
+
Choices.Add(LevelUpPanel.FindChildUI("Choice3"))
|
|
4329
|
+
|
|
4330
|
+
var FireballTimer = Timer()
|
|
4331
|
+
var PoisonballTimer = Timer()
|
|
4332
|
+
var MagicballTimer = Timer()
|
|
4333
|
+
var FireFieldTimer = Timer()
|
|
4334
|
+
|
|
4335
|
+
forever {
|
|
4336
|
+
// movement
|
|
4337
|
+
var dir = GetMoveVectorWorld()
|
|
4338
|
+
MoveInDirectionContinuouslyFacing(dir, 400)
|
|
4339
|
+
|
|
4340
|
+
// auto-attack
|
|
4341
|
+
if not LevelUpPanel.IsVisible() {
|
|
4342
|
+
// Fireball shoot nearest
|
|
4343
|
+
if FireballTimer.IsDone() and WeaponLevel >= 1 {
|
|
4344
|
+
FireballTimer.StartCountDown(AttackCooldown)
|
|
4345
|
+
var target = NearestObjectWithTag(Tag.Monster)
|
|
4346
|
+
if target.Exists() {
|
|
4347
|
+
var b = SpawnObject(SavedObject.snowball, GetPosition())
|
|
4348
|
+
b.Target = target
|
|
4349
|
+
b.Damage = AttackDamage
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
// Poisonball shoot faster, less damage
|
|
4353
|
+
if PoisonballTimer.IsDone() and WeaponLevel >= 2 {
|
|
4354
|
+
PoisonballTimer.StartCountDown(AttackCooldown * 0.65)
|
|
4355
|
+
var target = NearestObjectWithTag(Tag.Monster)
|
|
4356
|
+
if target.Exists() {
|
|
4357
|
+
var b = SpawnObject(SavedObject.poisonball, GetPosition())
|
|
4358
|
+
b.Target = target
|
|
4359
|
+
b.Speed = 500
|
|
4360
|
+
b.Damage = (AttackDamage * 0.5).Round()
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
// Magicball shoot multiple
|
|
4364
|
+
if MagicballTimer.IsDone() and WeaponLevel >= 3 {
|
|
4365
|
+
MagicballTimer.StartCountDown(AttackCooldown * 3.0)
|
|
4366
|
+
|
|
4367
|
+
var picks = GetAllObjectsWithTag(Tag.Monster).GetRandomElements(5)
|
|
4368
|
+
for t in picks {
|
|
4369
|
+
var f = SpawnObject(SavedObject.magicball, GetPosition())
|
|
4370
|
+
f.Target = t
|
|
4371
|
+
f.Speed = 300
|
|
4372
|
+
f.Damage = AttackDamage
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
// Fire field on impact: slow cooldown, persistent area damage
|
|
4376
|
+
if FireFieldTimer.IsDone() and WeaponLevel >= 4 {
|
|
4377
|
+
FireFieldTimer.StartCountDown(AttackCooldown * 5.5)
|
|
4378
|
+
|
|
4379
|
+
var target = NearestObjectWithTag(Tag.Monster)
|
|
4380
|
+
if target.Exists() {
|
|
4381
|
+
var s = SpawnObject(SavedObject.fireball, GetPosition())
|
|
4382
|
+
s.Target = target
|
|
4383
|
+
s.AreaObject = SavedObject.fireField
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
// level-up check
|
|
4389
|
+
if not LevelUpPanel.IsVisible() and XP >= NextLevelXP {
|
|
4390
|
+
XP -= NextLevelXP
|
|
4391
|
+
NextLevelXP += 5
|
|
4392
|
+
LevelUpPanel.SetVisible(true)
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
// take damage
|
|
4397
|
+
event WasAttacked {
|
|
4398
|
+
HP -= Event.Damage
|
|
4399
|
+
SetHoverHPBar(HP, MaxHP)
|
|
4400
|
+
if HP <= 0 {
|
|
4401
|
+
SpawnFloatingText("YOU DIED!", Vector(1,0,0))
|
|
4402
|
+
Destroy()
|
|
4403
|
+
} else {
|
|
4404
|
+
SpawnFloatingText("-" + Event.Damage, Vector(1,0,0))
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
// handle level-up via UI buttons
|
|
4408
|
+
event ClickButton {
|
|
4409
|
+
var choice = Choices.IndexOf(Event.ClickedButton) // -1 when not one of ours
|
|
4410
|
+
if choice == 0 {
|
|
4411
|
+
AttackCooldown = AttackCooldown * 0.9
|
|
4412
|
+
SpawnFloatingText("Cooldown -> " + AttackCooldown + " s", Vector(0, 1, 0))
|
|
4413
|
+
} else if choice == 1 {
|
|
4414
|
+
if WeaponLevel == 1 {
|
|
4415
|
+
SpawnFloatingText("Unlocked: Lv 2 Poison Orb", Vector(0, 1, 0))
|
|
4416
|
+
} else if WeaponLevel == 2 {
|
|
4417
|
+
SpawnFloatingText("Unlocked: Lv 3 Magic Orb", Vector(0, 1, 0))
|
|
4418
|
+
} else {
|
|
4419
|
+
SpawnFloatingText("Unlocked: Lv 4 Fire Field", Vector(0, 1, 0))
|
|
4420
|
+
}
|
|
4421
|
+
WeaponLevel = WeaponLevel + 1
|
|
4422
|
+
} else if choice == 2 {
|
|
4423
|
+
HP = (HP + 100).Min(MaxHP)
|
|
4424
|
+
SetHoverHPBar(HP, MaxHP)
|
|
4425
|
+
SpawnFloatingText("Healed! HP " + HP + "/" + MaxHP, Vector(0, 1, 0))
|
|
4426
|
+
} else {
|
|
4427
|
+
return
|
|
4428
|
+
}
|
|
4429
|
+
LevelUpPanel.SetVisible(false)
|
|
4430
|
+
}
|
|
4431
|
+
#end
|
|
4432
|
+
|
|
4433
|
+
|
|
4434
|
+
#class FireField
|
|
4435
|
+
var Radius = 150
|
|
4436
|
+
var Duration = 5.0
|
|
4437
|
+
var TickInterval = 0.5
|
|
4438
|
+
var DamagePerTick = 18
|
|
4439
|
+
|
|
4440
|
+
event OnSpawned {
|
|
4441
|
+
SnapToGroundWithTag(Tag.Ground)
|
|
4442
|
+
SetSize(Radius, Radius, 1)
|
|
4443
|
+
var LifeTimer = StartCountDownWithNewTimer(Duration)
|
|
4444
|
+
var TickTimer = Timer()
|
|
4445
|
+
forever {
|
|
4446
|
+
if LifeTimer.IsDone() { Destroy() }
|
|
4447
|
+
if TickTimer.IsDone() {
|
|
4448
|
+
TickTimer.StartCountDown(TickInterval)
|
|
4449
|
+
for m in GetObjectsWithinXYDistanceWithTag(Radius, Tag.Monster) {
|
|
4450
|
+
Attack(m, DamagePerTick)
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
#end
|
|
4456
|
+
|
|
4457
|
+
#class Projectile
|
|
4458
|
+
var Target : Object
|
|
4459
|
+
var Speed = 800
|
|
4460
|
+
var Damage = 1 // set later
|
|
4461
|
+
var AreaObject: Object
|
|
4462
|
+
event OnSpawned {
|
|
4463
|
+
forever {
|
|
4464
|
+
if not Target.Exists() { Destroy() }
|
|
4465
|
+
else {
|
|
4466
|
+
MoveTowardContinuousFacing(Target.GetPosition(), Speed)
|
|
4467
|
+
if SurfaceGapTo(Target) < 25 {
|
|
4468
|
+
if AreaObject != None {
|
|
4469
|
+
SpawnEffectWithAutoDestroy(Ori.alias.effect.snowball_burst, GetPosition())
|
|
4470
|
+
SpawnObject(AreaObject, GetPosition())
|
|
4471
|
+
} else {
|
|
4472
|
+
Attack(Target, Damage)
|
|
4473
|
+
}
|
|
4474
|
+
Destroy()
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
#end
|
|
4480
|
+
|
|
4481
|
+
|
|
4482
|
+
#class ExpGem
|
|
4483
|
+
var Value = 1
|
|
4484
|
+
var MagnetRange = 120
|
|
4485
|
+
var PullSpeed = 500
|
|
4486
|
+
var PickupRange = 40
|
|
4487
|
+
var TargetPlayer : Object
|
|
4488
|
+
var Pulled = false
|
|
4489
|
+
|
|
4490
|
+
event OnSpawned {
|
|
4491
|
+
|
|
4492
|
+
forever {
|
|
4493
|
+
if not Pulled {
|
|
4494
|
+
var p = NearestPlayer()
|
|
4495
|
+
if p.Exists() and SurfaceGapTo(p) < MagnetRange {
|
|
4496
|
+
Pulled = true
|
|
4497
|
+
TargetPlayer = p
|
|
4498
|
+
}
|
|
4499
|
+
} else {
|
|
4500
|
+
if not TargetPlayer.Exists() {
|
|
4501
|
+
TargetPlayer = NearestPlayer()
|
|
4502
|
+
if not TargetPlayer.Exists() { Pulled = false; continue }
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
MoveTowardContinuousHorizontal(TargetPlayer.GetPosition(), PullSpeed)
|
|
4506
|
+
|
|
4507
|
+
if SurfaceGapTo(TargetPlayer) < PickupRange {
|
|
4508
|
+
TargetPlayer.XP += Value
|
|
4509
|
+
TargetPlayer.SpawnFloatingText("+XP", Vector(0,1,0))
|
|
4510
|
+
Destroy()
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
#end
|
|
4516
|
+
|
|
4517
|
+
|
|
4518
|
+
#class HealPickup
|
|
4519
|
+
var HealAmount = 50
|
|
4520
|
+
var MagnetRange = 120
|
|
4521
|
+
var PullSpeed = 500
|
|
4522
|
+
var PickupRange = 40
|
|
4523
|
+
var TargetPlayer : Object
|
|
4524
|
+
var Pulled = false
|
|
4525
|
+
|
|
4526
|
+
event OnSpawned {
|
|
4527
|
+
forever {
|
|
4528
|
+
if not Pulled {
|
|
4529
|
+
var p = NearestPlayer()
|
|
4530
|
+
if p.Exists() and SurfaceGapTo(p) < MagnetRange {
|
|
4531
|
+
Pulled = true
|
|
4532
|
+
TargetPlayer = p
|
|
4533
|
+
}
|
|
4534
|
+
} else {
|
|
4535
|
+
if not TargetPlayer.Exists() {
|
|
4536
|
+
TargetPlayer = NearestPlayer()
|
|
4537
|
+
if not TargetPlayer.Exists() {
|
|
4538
|
+
Pulled = false
|
|
4539
|
+
continue
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
MoveTowardContinuousHorizontal(TargetPlayer.GetPosition(), PullSpeed)
|
|
4543
|
+
|
|
4544
|
+
if SurfaceGapTo(TargetPlayer) < PickupRange {
|
|
4545
|
+
TargetPlayer.HP = (TargetPlayer.HP + HealAmount).Min(TargetPlayer.MaxHP)
|
|
4546
|
+
TargetPlayer.SpawnFloatingText("+HP", Vector(0,1,0))
|
|
4547
|
+
TargetPlayer.SetHoverHPBar(TargetPlayer.HP, TargetPlayer.MaxHP)
|
|
4548
|
+
Destroy()
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
#end
|
|
4554
|
+
|
|
4555
|
+
#class MeleeEnemy
|
|
4556
|
+
var HP = 80
|
|
4557
|
+
var MaxHP = 80
|
|
4558
|
+
var Damage = 12
|
|
4559
|
+
var MeleeRange = 75
|
|
4560
|
+
var Speed = 70
|
|
4561
|
+
var AttackCooldown = 1.0
|
|
4562
|
+
var ExpDropCount = 1
|
|
4563
|
+
var DropHealChancePercent = 0
|
|
4564
|
+
|
|
4565
|
+
event OnPreSpawn {
|
|
4566
|
+
AddTag(Tag.Monster)
|
|
4567
|
+
}
|
|
4568
|
+
event OnSpawned {
|
|
4569
|
+
HP = MaxHP // in case of override
|
|
4570
|
+
SetHoverHPBar(HP, MaxHP)
|
|
4571
|
+
|
|
4572
|
+
var AttackTimer = Timer()
|
|
4573
|
+
forever {
|
|
4574
|
+
var p = NearestPlayer()
|
|
4575
|
+
if p.Exists() {
|
|
4576
|
+
MoveTowardContinuousHorizontalFacing(p.GetPosition(), Speed)
|
|
4577
|
+
if AttackTimer.IsDone() and SurfaceGapTo(p) < MeleeRange {
|
|
4578
|
+
AttackTimer.StartCountDown(AttackCooldown)
|
|
4579
|
+
PlayAttackAnimation()
|
|
4580
|
+
Attack(p, Damage)
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
event WasAttacked {
|
|
4586
|
+
HP -= Event.Damage
|
|
4587
|
+
SpawnFloatingText("-" + Event.Damage, Vector(1,0,0))
|
|
4588
|
+
SetHoverHPBar(HP, MaxHP)
|
|
4589
|
+
if HP <= 0 {
|
|
4590
|
+
if RandomChancePercent(DropHealChancePercent) {
|
|
4591
|
+
SpawnObject(SavedObject.healPickup, GetPosition())
|
|
4592
|
+
}
|
|
4593
|
+
for i in 0..ExpDropCount {
|
|
4594
|
+
SpawnObject(SavedObject.expGem, GetPosition().ShiftX(i * 10))
|
|
4595
|
+
}
|
|
4596
|
+
Die()
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
#end
|
|
4600
|
+
|
|
4601
|
+
#class EnemyManager
|
|
4602
|
+
var SpawnInterval = 2
|
|
4603
|
+
var MaxAlive = 30
|
|
4604
|
+
var DespawnDistance = 1500
|
|
4605
|
+
|
|
4606
|
+
event OnSpawned {
|
|
4607
|
+
var SpawnTimer = StartCountDownWithNewTimer(SpawnInterval)
|
|
4608
|
+
var DespawnTmr = StartCountDownWithNewTimer(2)
|
|
4609
|
+
forever {
|
|
4610
|
+
// Despawn monsters too far from every player
|
|
4611
|
+
if DespawnTmr.IsDone() {
|
|
4612
|
+
for m in GetAllObjectsWithTag(Tag.Monster) {
|
|
4613
|
+
var nearest = 999999
|
|
4614
|
+
for p in GetAllPlayers() {
|
|
4615
|
+
nearest = nearest.Min(m.DistanceTo(p))
|
|
4616
|
+
}
|
|
4617
|
+
if nearest > DespawnDistance { m.Destroy() }
|
|
4618
|
+
}
|
|
4619
|
+
DespawnTmr.StartCountDown(2)
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
// Spawn new monsters but respect cap
|
|
4623
|
+
if SpawnTimer.IsDone() {
|
|
4624
|
+
var p = NearestPlayer()
|
|
4625
|
+
if p.Exists() {
|
|
4626
|
+
if MaxAlive - GetAllObjectsWithTag(Tag.Monster).Count() > 0 {
|
|
4627
|
+
var pos = p.GetPosition()
|
|
4628
|
+
.ShiftX(RandomNumber(-600,600))
|
|
4629
|
+
.ShiftY(RandomNumber(-600,600))
|
|
4630
|
+
var roll = RandomNumber(0,100)
|
|
4631
|
+
if roll < 30 { SpawnObject(SavedObject.goblin, pos) }
|
|
4632
|
+
else if roll < 60 { SpawnObject(SavedObject.raptor, pos) }
|
|
4633
|
+
else if roll < 90 { SpawnObject(SavedObject.fox , pos) }
|
|
4634
|
+
else { SpawnObject(SavedObject.dragon, pos) }
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
SpawnTimer.StartCountDown(SpawnInterval)
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
#end
|
|
4642
|
+
|
|
4643
|
+
saved fireball {
|
|
4644
|
+
effect fireball : Projectile {
|
|
4645
|
+
effect = "alias.effect.fireball"
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
saved poisonball {
|
|
4649
|
+
effect poisonball : Projectile {
|
|
4650
|
+
effect = "alias.effect.poisonball"
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
saved magicball {
|
|
4654
|
+
effect magicball : Projectile {
|
|
4655
|
+
effect = "alias.effect.magicball"
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
saved snowball {
|
|
4659
|
+
effect snowball : Projectile {
|
|
4660
|
+
effect = "alias.effect.snowball"
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
saved fireField {
|
|
4664
|
+
effect fireField : FireField {
|
|
4665
|
+
effect = "alias.effect.fire_area"
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
saved expGem {
|
|
4669
|
+
display gem : ExpGem { model="alias.model.exp_gem" size=14 color=(0,1,1) tag=Tag.ExpGem }
|
|
4670
|
+
}
|
|
4671
|
+
saved healPickup {
|
|
4672
|
+
display chicken : HealPickup { model="alias.model.heal_pickup" size=12 color=(1,0.8,0.3) tag=Tag.Item }
|
|
4673
|
+
}
|
|
4674
|
+
saved goblin {
|
|
4675
|
+
crowd goblin : MeleeEnemy {
|
|
4676
|
+
model = "alias.model.goblin",
|
|
4677
|
+
height = 50,
|
|
4678
|
+
override MaxHP = 80,
|
|
4679
|
+
override Damage = 12,
|
|
4680
|
+
override MeleeRange = 75,
|
|
4681
|
+
override Speed = 70,
|
|
4682
|
+
override AttackCooldown = 1.0
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
saved fox {
|
|
4686
|
+
crowd fox : MeleeEnemy {
|
|
4687
|
+
model = "alias.model.fox"
|
|
4688
|
+
height = 60
|
|
4689
|
+
override MaxHP = 150
|
|
4690
|
+
override Damage = 8
|
|
4691
|
+
override MeleeRange = 60
|
|
4692
|
+
override Speed = 140
|
|
4693
|
+
override AttackCooldown = 0.8
|
|
4694
|
+
override DropHealChancePercent = 20
|
|
4695
|
+
override ExpDropCount = 2
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
saved raptor {
|
|
4699
|
+
crowd raptor : MeleeEnemy {
|
|
4700
|
+
model = "alias.model.raptor"
|
|
4701
|
+
height = 70
|
|
4702
|
+
override MaxHP = 120
|
|
4703
|
+
override Damage = 20
|
|
4704
|
+
override MeleeRange = 80
|
|
4705
|
+
override Speed = 180
|
|
4706
|
+
override AttackCooldown = 0.8
|
|
4707
|
+
override DropHealChancePercent = 10
|
|
4708
|
+
override ExpDropCount = 2
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
saved dragon {
|
|
4712
|
+
crowd dragon : MeleeEnemy {
|
|
4713
|
+
model = "alias.model.dragon"
|
|
4714
|
+
height = 200
|
|
4715
|
+
override MaxHP = 1000
|
|
4716
|
+
override Damage = 80
|
|
4717
|
+
override MeleeRange = 120
|
|
4718
|
+
override Speed = 50
|
|
4719
|
+
override AttackCooldown = 1.5
|
|
4720
|
+
override DropHealChancePercent = 100
|
|
4721
|
+
override ExpDropCount = 10
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4725
|
+
material GroundMat { base = "StylizedGrass" }
|
|
4726
|
+
object ground { model="cube" material="GroundMat" position=(0, 0, -100) size=(10000, 10000, 100) tag = Tag.Ground}
|
|
4727
|
+
display manager: EnemyManager { visible = false }
|
|
4728
|
+
|
|
4729
|
+
screenCanvas LevelUpUI {
|
|
4730
|
+
// vStack with no size hugs its children (200x260 here); children set explicit sizes. Center-anchored modal.
|
|
4731
|
+
vStack LevelUpPanel {
|
|
4732
|
+
anchor = (center, center)
|
|
4733
|
+
padding = 10
|
|
4734
|
+
gap = 10
|
|
4735
|
+
color = #1A1024F0
|
|
4736
|
+
corner_radius = 14
|
|
4737
|
+
border_width = 1
|
|
4738
|
+
border_color = #6A3FB0FF
|
|
4739
|
+
textLabel Title { size=(180, 30) align=(center, center) text="Leveled Up!" }
|
|
4740
|
+
button Choice1 { size=(180, 60) text="-10% Cooldown" corner_radius=10 }
|
|
4741
|
+
button Choice2 { size=(180, 60) text="+Elemental Orb" corner_radius=10 }
|
|
4742
|
+
button Choice3 { size=(180, 60) text="Heal 100 HP" corner_radius=10 }
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
screenCanvas TutorialUI {
|
|
4746
|
+
textLabel Text {
|
|
4747
|
+
offset = (20, 80)
|
|
4748
|
+
text = "Move with WASD or mobile thumbstick"
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
</example_vampire_survivor>
|
|
4753
|
+
|
|
4754
|
+
|
|
2045
4755
|
|
|
2046
4756
|
</world_description_examples>
|
|
2047
4757
|
|
|
@@ -2060,6 +4770,7 @@ Worlds may carry `#doc_version N` = the doc version they were last authored/upgr
|
|
|
2060
4770
|
- Cannot do ClassName() to construct object
|
|
2061
4771
|
- No emoticon in Text and comments
|
|
2062
4772
|
- only use '//' for comments
|
|
4773
|
+
- Destroy() also ends the current function body (a value-returning func returns its type default); the caller keeps running this frame.
|
|
2063
4774
|
- Object member calls (including Destroy()) on a None or already-despawned Object are runtime errors (a second Destroy() before the queued despawn runs is fine). Avoid spamming Exists(); prefer fail-fast. Use Exists() only when absence is expected (mouse/crosshair miss, destroyed mid-loop). Note: Object.Exists() is safe on None (returns false), so `o == None or not o.Exists()` is redundant; write `not o.Exists()`.
|
|
2064
4775
|
- Reference a named scene object with Scene.X (e.g. Scene.Hotbar, Scene.EnemyBase.SetVisible(true)). It resolves once at world load; Exists() is false if no such object. Runtime-spawned objects are not reachable this way - keep the SpawnObject result in a var.
|
|
2065
4776
|
- Never write saved_origin inside a saved clause (wrong: saved BrickBlock { object BrickBlock { saved_origin = "BrickBlock" } })
|
|
@@ -2077,10 +4788,10 @@ Weave2 is the only engine: a world with '#set Weave2 0' at the top refuses to lo
|
|
|
2077
4788
|
- Remove the '#set Weave2 0' line, recompile, and fix diagnostics; the language is the same, typing is stricter.
|
|
2078
4789
|
- A saved asset stored under Weave1 fails spawn with "saved clone VARS reference a retired Weave1 code chunk": open the world that owns it, fix its class diagnostics, and re-save the asset under Weave2.
|
|
2079
4790
|
- W2C0605 unknown function, or W2C0614 whose message says the simcall is not in the allowlist: that simcall isn't ported to Weave2 (most have a ported replacement — see below). W2C0614 is also used for other type/usage errors; read the message, not just the code.
|
|
2080
|
-
- "retired in Weave2" errors name the replacement in the message; apply it at the call site. Common ones: HasArrivedXY -> DistanceXYTo(goal) <= radius; RotateTowardContinuous(Horizontal) -> LookAtHorizontal or MoveTowardContinuousHorizontalFacing.
|
|
4791
|
+
- "retired in Weave2" errors name the replacement in the message; apply it at the call site. Common ones: HasArrivedXY -> DistanceXYTo(goal) <= radius; RotateTowardContinuous(Horizontal) -> LookAtHorizontal or MoveTowardContinuousHorizontalFacing; ResetLogic/ResetAllLogic -> your own re-init func (no engine call re-runs OnSpawned).
|
|
2081
4792
|
- Never work around a missing or retired simcall by defining a local func with its name (e.g. an empty 'func SetCastShadow(enabled: Bool) { }'): it silently disables engine behavior and breaks again when the engine call changes. Migrate the call site, or stop and report the gap.
|
|
2082
4793
|
- Over-time controllers (MoveToOverTime etc.) and Move*Continuously directional variants are retired: drive movement from an Update loop or MoveInDirectionContinuously.
|
|
2083
|
-
- TeleportUp/Down/Left/Right/Forward/Backward, TeleportCenterTo, TeleportOnTopOf, TeleportGroupWithPivot are retired: use TeleportTo with a computed Position, e.g. TeleportTo(GetPosition().ShiftUp(300)) or TeleportTo(Base.GetPosition().ShiftUp(Base.
|
|
4794
|
+
- TeleportUp/Down/Left/Right/Forward/Backward, TeleportCenterTo, TeleportOnTopOf, TeleportGroupWithPivot are retired: use TeleportTo with a computed Position, e.g. TeleportTo(GetPosition().ShiftUp(300)) or TeleportTo(Base.GetPosition().ShiftUp(Base.GetHeight())).
|
|
2084
4795
|
- SetDisplayPosition/SetDisplayCenterPosition are legacy and DisplayObject-only (worldCanvas tags etc.); on any other receiver they hard-error at spawn. Prefer SetPosition/SetCenterPosition (any object); for a visual-only offset that leaves the collider in place, use SetRenderOffset.
|
|
2085
4796
|
- Untyped 'var X: List' now infers element type; annotate 'List<T>' if inference errors (W2C0620/W2C0621). Lists can now hold Vector/Position/Timer/class types directly (un-unroll old parallel-array workarounds).
|
|
2086
4797
|
- A migration pass must be behavior-preserving: port call sites 1:1 and verify, then do architecture cleanups (pooling, query changes, debug removal) as a separate pass.
|