Park/Areas/AreaBuilder.cs
using HC3.Terrain;
using System;
namespace HC3;
public enum AreaType
{
Enclosure
}
internal class AreaBuilder : Component, Component.ExecuteInEditor
{
public AreaType AreaType { get; set; }
public static AreaBuilder Instance { get; private set; }
public GameObject Ghost { get; set; }
[Property]
public PathTileMap EnclosureTileMap { get; internal set; }
protected override void OnAwake()
{
Instance = this;
}
protected override void OnEnabled()
{
UpdateGhost();
}
protected override void OnDisabled()
{
Ghost?.Destroy();
}
public void UpdateGhost()
{
Ghost?.DestroyImmediate();
Ghost = new GameObject( false, "Ghost Area" );
Ghost.Flags |= GameObjectFlags.NotNetworked;
// For now
var area = Ghost.AddComponent<AreaTile>();
var gridObject = Ghost.GetOrAddComponent<GridObject>();
gridObject.IsWalkable = false;
gridObject.BlocksConstruction = false;
area.GhostPreview = true;
area.AreaType = AreaType;
Ghost.Enabled = true;
}
bool startedDragging = false;
Vector3 dragStartPos;
Vector3 dragEndPos;
List<GameObject> Ghosts = new();
void UpdateGhosts()
{
Ghosts.ForEach( g => g.DestroyImmediate() );
Ghosts.Clear();
var startGrid = GridManager.WorldToGridPosition( dragStartPos );
var endGrid = GridManager.WorldToGridPosition( dragEndPos );
var min = Vector2Int.Min( startGrid, endGrid );
var max = Vector2Int.Max( startGrid, endGrid );
for ( int x = min.x; x <= max.x; x++ )
{
for ( int y = min.y; y <= max.y; y++ )
{
var worldPos = GridManager.GridToWorldPosition( new Vector2Int( x, y ) );
var height = GridManager.Instance.Terrain[new Vector2Int( x, y )].MaxHeight;
worldPos = worldPos.WithZ( height * GridManager.Instance.Terrain.TileHeight ) + GridManager.CentreOffset;
var ghost = new GameObject( false, "Ghost Area" );
ghost.Flags |= GameObjectFlags.NotNetworked;
ghost.WorldPosition = worldPos;
var area = ghost.AddComponent<AreaTile>();
area.GhostPreview = true;
area.AreaType = AreaType;
var gridObject = ghost.GetOrAddComponent<GridObject>();
gridObject.IsWalkable = false;
gridObject.BlocksConstruction = false;
ghost.Enabled = true;
Ghosts.Add( ghost );
}
}
}
protected override void OnUpdate()
{
if ( Scene.Camera is not { } camera ) return;
if ( !Mouse.Active ) return;
var ray = camera.ScreenPixelToRay( Mouse.Position );
var result = Scene.Trace
.Ray( ray, 65536f )
.UsePhysicsWorld()
.WithTag( "ground" )
.Run();
if ( !result.Hit || result.Component is not TerrainMesh { Terrain: var terrain } ) return;
var gridPos = GridManager.WorldToGridPosition( result.HitPosition );
var snappedPos = GridManager.GridToWorldPosition( gridPos );
var height = terrain[gridPos].MaxHeight;
// finish current dragging
if ( !Input.Down( "Attack1" ) && startedDragging )
{
startedDragging = false;
List<Vector3> areasToPlace = new();
foreach ( var ghost in Ghosts )
{
areasToPlace.Add( ghost.WorldPosition );
ghost.DestroyImmediate();
}
Ghosts.Clear();
PlaceMany( dragStartPos, dragEndPos );
}
// Dragging
if ( Input.Down( "Attack1" ) )
{
if ( !startedDragging )
{
startedDragging = true;
dragStartPos = snappedPos.WithZ( terrain.TileHeight * height ) + new Vector3( 32, 32, 0 );
}
// keep drag end pos in line with start pos
var endPos = snappedPos.WithZ( terrain.TileHeight * height ) + new Vector3( 32, 32, 0 );
var diff = endPos - dragStartPos;
dragEndPos = endPos;
Ghost.WorldPosition = dragEndPos;
// prevent z-fighting with existing areas
Ghost.WorldPosition += Vector3.Up * 0.1f;
UpdateGhosts();
}
else
{
Ghost.WorldPosition = snappedPos.WithZ( terrain.TileHeight * height ) + new Vector3( 32, 32, 0 );
}
if ( Input.Pressed( "Attack2" ) )
{
Delete( Ghost.WorldPosition );
}
}
public static bool IsareaableSlope( TileSlope slope )
{
var gradients = slope.GetGradients();
// Flat only
return gradients == Vector2Int.Zero;
}
/// <summary>
/// Place a new area. This is called on the host.
/// </summary>
[Rpc.Host]
public void Place( Vector3 pos, AreaType type )
{
var gridPos = GridManager.WorldToGridPosition( pos );
var cost = 10;
// Can we afford to place the area?
if ( !ParkManager.Instance?.TakeMoney( cost, "Areas" ) ?? false )
return;
if ( GridManager.Instance?.Terrain[gridPos] is not { } tile )
return;
if ( !BuildingZone.Instance.IsOwned( pos ) )
return;
if ( !IsareaableSlope( tile.Slope ) )
{
GridManager.Instance.Terrain[gridPos] = TerrainTile.LevelGround( tile.MaxHeight );
}
if ( AddArea( gridPos, type ) is null )
return;
var dust = GameObject.Clone( "prefabs/particles/place_dustcloud.prefab", new Transform( pos + Vector3.Up * 8 ) );
Sound.Play( "sounds/gameplay/building_placed.sound", pos );
MoneyEffect.Broadcast( pos + Vector3.Up * 10f, $"-{GameUtils.Currency}{cost}", Color.Red );
}
public AreaTile AddArea( Vector2Int tilePosition, AreaType type = AreaType.Enclosure )
{
var grid = Scene.GetAllComponents<GridManager>()
.FirstOrDefault() ?? throw new Exception( "Grid doesn't exist!" );
var terrain = grid.Terrain;
var go = new GameObject( true, "area" );
var level = terrain[tilePosition].MaxHeight;
int height = 2;
if ( grid.IsConstructionBlocked( new Vector3Int( tilePosition ).WithZ( level ), height ) ) return null;
go.WorldPosition = terrain.GridToWorld( new( tilePosition + 0.5f, level ) );
go.Tags.Add( "area" );
var area = go.AddComponent<AreaTile>();
area.AreaType = type;
var gridObject = go.GetOrAddComponent<GridObject>();
gridObject.IsWalkable = true;
gridObject.BlocksConstruction = true;
if ( tilePosition.x == terrain.Bounds.Left )
{
// spawn guests from here!
gridObject.AddComponent<SpawnPoint>();
}
if ( Scene.IsEditor )
{
go.Flags |= GameObjectFlags.NotSaved;
}
else if ( Networking.IsHost )
{
// Network spawn this area object if we're the host
go.NetworkSpawn();
}
return area;
}
public async void PlaceMany( Vector3 start, Vector3 end )
{
var startGrid = GridManager.WorldToGridPosition( start );
var endGrid = GridManager.WorldToGridPosition( end );
var min = Vector2Int.Min( startGrid, endGrid );
var max = Vector2Int.Max( startGrid, endGrid );
for ( int x = min.x; x <= max.x; x++ )
{
for ( int y = min.y; y <= max.y; y++ )
{
var world = GridManager.GridToWorldPosition( new Vector2Int( x, y ) ) + GridManager.CentreOffset;
Place( world, AreaType );
await Task.DelayRealtime( 5 );
}
}
}
[Rpc.Host]
public void Delete( Vector3 pos )
{
if ( !BuildingZone.Instance.IsOwned( pos ) ) return;
var gridPos = GridManager.WorldToGridPosition( pos );
var grid = GridManager.Instance;
var areaCost = 10;
if ( grid.GetCell( gridPos )?.GetComponents<AreaTile>()?.Where( x => !x.GhostPreview ).FirstOrDefault() is not { } area )
return;
area.GameObject.DestroyImmediate();
MoneyEffect.Broadcast( pos + Vector3.Up * 10f, $"-{GameUtils.Currency}{areaCost}", Color.Green );
ParkManager.Instance?.GiveMoney( areaCost, "Building Refunds" );
}
internal void Clear()
{
}
internal bool CanPlace( Vector2Int tilePosition )
{
var level = GridManager.Instance.Terrain[tilePosition].MaxHeight;
int height = 2;
if ( GridManager.Instance.IsConstructionBlocked( new Vector3Int( tilePosition ).WithZ( level ), height ) ) return false;
return true;
}
}