// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using ClassicalSharp.Entities; using OpenTK; #if USE16_BIT using BlockID = System.UInt16; #else using BlockID = System.Byte; #endif namespace ClassicalSharp.Particles { public abstract class CollidableParticle : Particle { protected bool hitTerrain = false, throughLiquids = true; public void ResetState(Vector3 pos, Vector3 velocity, double lifetime) { Position = lastPos = nextPos = pos; Velocity = velocity; Lifetime = (float)lifetime; hitTerrain = false; } protected bool Tick(Game game, float gravity, double delta) { hitTerrain = false; lastPos = Position = nextPos; BlockID curBlock = GetBlock(game, (int)Position.X, (int)Position.Y, (int)Position.Z); float minY = Utils.Floor(Position.Y) + game.BlockInfo.MinBB[curBlock].Y; float maxY = Utils.Floor(Position.Y) + game.BlockInfo.MaxBB[curBlock].Y; if (!CanPassThrough(game, curBlock) && Position.Y >= minY && Position.Y < maxY && CollideHor(game, curBlock)) return true; Velocity.Y -= gravity * (float)delta; int startY = (int)Math.Floor(Position.Y); Position += Velocity * (float)delta * 3; int endY = (int)Math.Floor(Position.Y); if (Velocity.Y > 0) { // don't test block we are already in for (int y = startY + 1; y <= endY && TestY(game, y, false); y++); } else { for (int y = startY; y >= endY && TestY(game, y, true); y--); } nextPos = Position; Position = lastPos; return base.Tick(game, delta); } bool TestY(Game game, int y, bool topFace) { if (y < 0) { Position.Y = nextPos.Y = lastPos.Y = 0 + Entity.Adjustment; Velocity = Vector3.Zero; hitTerrain = true; return false; } BlockID block = GetBlock(game, (int)Position.X, y, (int)Position.Z); if (CanPassThrough(game, block)) return true; Vector3 minBB = game.BlockInfo.MinBB[block]; Vector3 maxBB = game.BlockInfo.MaxBB[block]; float collideY = y + (topFace ? maxBB.Y : minBB.Y); bool collideVer = topFace ? (Position.Y < collideY) : (Position.Y > collideY); if (collideVer && CollideHor(game, block)) { float adjust = topFace ? Entity.Adjustment : -Entity.Adjustment; Position.Y = nextPos.Y = lastPos.Y = collideY + adjust; Velocity = Vector3.Zero; hitTerrain = true; return false; } return true; } bool CanPassThrough(Game game, BlockID block) { byte draw = game.BlockInfo.Draw[block]; return draw == DrawType.Gas || draw == DrawType.Sprite || (throughLiquids && game.BlockInfo.IsLiquid(block)); } bool CollideHor(Game game, BlockID block) { Vector3 min = game.BlockInfo.MinBB[block] + FloorHor(Position); Vector3 max = game.BlockInfo.MaxBB[block] + FloorHor(Position); return Position.X >= min.X && Position.Z >= min.Z && Position.X < max.X && Position.Z < max.Z; } BlockID GetBlock(Game game, int x, int y, int z) { if (game.World.IsValidPos(x, y, z)) return game.World.GetBlock(x, y, z); if (y >= game.World.Env.EdgeHeight) return Block.Air; if (y >= game.World.Env.SidesHeight) return game.World.Env.EdgeBlock; return game.World.Env.SidesBlock; } static Vector3 FloorHor(Vector3 v) { return new Vector3(Utils.Floor(v.X), 0, Utils.Floor(v.Z)); } } }