public Entity(EntityCombatAI combatAI, EntityTaskAI taskAI, EntityMovementData movementData, string name = "un-named_entity", bool isFixed = false) { Name = name; IsFixed = isFixed; SetPosition(Vector3.zero); EntityAI = new EntityAI(this, combatAI, taskAI); Inventory = new Inventory(); CombatManager = new EntityCombatManager(this); SkillTree = new SkillTree(); MovementData = movementData; }
public Entity(EntityCombatAI combatAI, EntityTaskAI taskAI, EntityMovementData movementData, string name = "un-named_entity", bool isFixed = false) { Name = name; IsFixed = isFixed; // MoveEntity(Vector3.zero); Debug.Log("Base entity created"); EntityAI = new EntityAI(this, combatAI, taskAI); Inventory = new Inventory(); CombatManager = new EntityCombatManager(this); SkillTree = new SkillTree(); MovementData = movementData; CurrentSubworldID = -1; IsAlive = true; }
// Checks how far this entity can move along the path without hitting a map obstruction. If an // obstruction is encountered, it edits the movement data to account for it. protected virtual void try_to_move_obs(ref EntityMovementData data) { Point closest_obs = hitbox.Location; int max_distance; Point pixel_path, sign, farthest; // The target pixel is where the character's upper-left hitbox pixel will be if it // moves the full possible distance. pixel_path = new Point(((int)Math.Floor(_exact_pos.X + data.attempted_path.X)) - hitbox.X, ((int)Math.Floor(_exact_pos.Y + data.attempted_path.Y)) - hitbox.Y); sign = new Point(Math.Sign(pixel_path.X), Math.Sign(pixel_path.Y)); if (sign.X == 0 && sign.Y == 0) return; // no between-pixel movement; farthest = new Point(hitbox.X + pixel_path.X, hitbox.Y + pixel_path.Y); max_distance = Int32.MaxValue; if (sign.X != 0) { // moving horizontally -- check collision with left or right side _try_move_project_side(pixel_path, true, ref max_distance, ref farthest); } if (sign.Y != 0) { // moving vertically -- check collision with top or bottom _try_move_project_side(pixel_path, false, ref max_distance, ref farthest); } if (max_distance < Int32.MaxValue) { // obstructed data.collided = true; data.actual_path = new Vector2((float)farthest.X - hitbox.X, (float)farthest.Y - hitbox.Y); return; } }
// A helper for try_to_move(). This checks the prospective path for obstructing // entities and returns a new path indicating how far the entity can move without // hitting one. protected virtual void try_to_move_ent(ref EntityMovementData data) { Point pixel_path, best_path, cur_path; Rectangle goal; int best_distance, cur_distance; VERGEMap map = VERGEGame.game.map; BoundedSpace<Entity>.BoundedElementSet ent_enum; Entity ent, nearest_ent = null; // The target pixel is where the character's upper-left hitbox pixel will be if it // moves the full possible distance. pixel_path = new Point(((int)Math.Floor(_exact_pos.X + data.attempted_path.X)) - hitbox.X, ((int)Math.Floor(_exact_pos.Y + data.attempted_path.Y)) - hitbox.Y); best_distance = Math.Abs(pixel_path.X) + Math.Abs(pixel_path.Y); if (best_distance <= 0) return; // no between-pixel movement; best_path = pixel_path; goal = hitbox; goal.Offset(pixel_path.X, pixel_path.Y); ent_enum = VERGEGame.game.entity_space.elements_within_bounds(Rectangle.Union(hitbox, goal), true, this); while (ent_enum.GetNext(out ent)) { if (ent.obstructing && ent.visible) { cur_path = pixel_path; if (test_collision(ent.hitbox, ref cur_path)) { cur_distance = Math.Abs(cur_path.X) + Math.Abs(cur_path.Y); System.Diagnostics.Debug.Assert(cur_distance <= Math.Abs(pixel_path.X) + Math.Abs(pixel_path.Y)); // THis should always be strictly < but sometimes it's not???hmm if (cur_distance < best_distance) { best_distance = cur_distance; best_path = cur_path; nearest_ent = ent; } } } } if (nearest_ent != null) { data.collided = true; data.collided_entity = nearest_ent; data.actual_path.X = (float)best_path.X; data.actual_path.Y = (float)best_path.Y; } }
public virtual void try_to_move(ref EntityMovementData data) { try_to_move_obs(ref data); try_to_move_ent(ref data); return; }
public override void Update() { // This function, like the movement handlers, mostly works with "speed-adjusted" rather than real time. // A unit difference in the time variables it employs corresponds to a difference of 1/Entity.speed ticks. int elapsed; float time_factor; double root1, root2; Vector2 velocity_change; EntityMovementData data = new EntityMovementData(); data.time = ( VERGEGame.game.tick - last_logic_tick ) * speed; data.time_shortfall = 0; data.first_call = true; data.collided = false; data.collided_entity = null; data.actual_path = data.attempted_path = Vector2.Zero; data.starting_point = _exact_pos; time_factor = 0f; // Now for the gross part. Maybe this should be moved to another function? while( data.time > 0 ) { data.time_shortfall = elapsed = data.time - handler( this, move_state, ref data ); time_factor = ( (float)elapsed ) / speed; // time passed this step, in ticks, as a float. velocity_change = time_factor * acceleration; data.actual_path = data.attempted_path = time_factor * ( velocity + velocity_change / 2 ); // cur_pos = old_pos + v*t + a*(t^2)/2 if( this.obstructable ) { if( data.attempted_path != Vector2.Zero ) { data.collided = false; data.collided_entity = null; try_to_move( ref data ); // maybe truncate actual_path if( data.collided ) { // If the entity was interrupted by a collision, "give back" some of its spent time if( data.actual_path == Vector2.Zero ) { elapsed = 0; velocity_change = Vector2.Zero; } // If acceleration is 0, we can just interpolate. else if( acceleration == Vector2.Zero ) { // we only need to look at one dimension, so we'll take the bigger one if( Math.Abs( data.actual_path.X ) > Math.Abs( data.actual_path.Y ) ) elapsed = (int)Math.Ceiling( elapsed * data.actual_path.X / data.attempted_path.X ); else elapsed = (int)Math.Ceiling( elapsed * data.actual_path.Y / data.attempted_path.Y ); } else { // Okay, hunker down because this is the gross part. When acceleration is a nonzero // constant, we can't simply interpolate but must instead solve a quadratic equation // in the elapsed time: new_pos = A/2*t^2 + V*t + old_pos. The constraints of the // scenario ensure that there is at least one positive root, but there could also be // two, in which case we want the smallest positive root, which represents the earliest // time in the future that the obstruction will be hit. // we only need to look at one dimension, so we'll take the bigger one if( Math.Abs( data.actual_path.X ) > Math.Abs( data.actual_path.Y ) ) { root1 = Math.Sqrt( velocity.X * velocity.X + 2 * acceleration.X * data.actual_path.X ); root2 = ( -velocity.X + root1 ) / acceleration.X; root1 = ( -velocity.X - root1 ) / acceleration.X; } else { root1 = Math.Sqrt( velocity.Y * velocity.Y + 2 * acceleration.Y * data.actual_path.Y ); root2 = ( -velocity.Y + root1 ) / acceleration.Y; root1 = ( -velocity.Y - root1 ) / acceleration.Y; } // Set root1 to the root we actually want (smallest positive). if( root1 < 0 || root1 > root2 && root2 > 0 ) root1 = root2; elapsed = (int)Math.Ceiling( root1 * speed ); velocity_change = ( (float)elapsed ) / speed * acceleration; } } } else if( elapsed < data.time ) { data.collided = false; data.collided_entity = null; } } if( data.actual_path != Vector2.Zero ) { exact_x += data.actual_path.X; exact_y += data.actual_path.Y; } velocity += velocity_change; data.time_shortfall -= elapsed; data.time -= elapsed; data.first_call = false; } if( data.collided ) { time_pushing += time_factor; // Pushing-related triggers happen here. if( movestring != null ) { if( movestring.timeout != Movestring.NEVER_TIMEOUT && time_pushing > movestring.timeout ) movestring.do_timeout(); } } else time_pushing = 0f; VERGEGame.game.entity_space.Update( this ); last_logic_tick = VERGEGame.game.tick; }
public DungeonBoss(EntityCombatAI combatAI, EntityTaskAI taskAI, EntityMovementData movementData = default(EntityMovementData), string name = "un-named_entity", bool isFixed = false) : base(combatAI, taskAI, movementData, name, isFixed) { }
public HumanoidEntity(EntityCombatAI combatAI, EntityTaskAI taskAI, EntityMovementData movementData, string name = "un-named_entity", bool isFixed = false) : base(combatAI, taskAI, movementData, name, isFixed) { EquiptmentManager = new EquiptmentManager(this); }
public static int entity_movescript_handler(Entity ent, Object state, ref EntityMovementData data) { int cur_param, time_left; Movestring movestring; ent.velocity = Vector2.Zero; ent.acceleration = Vector2.Zero; time_left = data.time; movestring = ent.movestring; // Movement failed. As with VERGE, the standard policy is just to mash up against the obstruction forever // and hope it goes away somehow. if (data.collided) { if (movestring.movement_left == Int32.MinValue) movestring.movement_left = data.time_shortfall; else movestring.movement_left += data.time_shortfall; return 0; } while (time_left > 0) { if (movestring.movement_left == Int32.MinValue) { movestring.movement_left = 0; movestring.step++; } time_left = movestring.ready(time_left); if (time_left <= 0 && ent.moving) ent.set_walk_state(false); if (movestring.movement_left <= 0) { // If not currently walking cur_param = movestring.parameters[movestring.step]; switch (movestring.commands[movestring.step]) { case MovestringCommand.Face: ent.facing = Utility.movestring_face_to_direction(cur_param); movestring.step++; break; case MovestringCommand.Frame: if (cur_param >= 0) ent.set_frame(cur_param); else ent.resume_animation(); movestring.step++; break; case MovestringCommand.ToX: if (movestring.tile_movement) cur_param *= game.map.tileset.tilesize; cur_param -= ent.x; // convert to relative position if (cur_param < 0) // Target is to the left of entity set_up_cardinal_movement(ent, Direction.Left, -cur_param, false); else // Target is to the right of entity set_up_cardinal_movement(ent, Direction.Right, cur_param, false); break; case MovestringCommand.ToY: if (movestring.tile_movement) cur_param *= game.map.tileset.tilesize; cur_param -= ent.y; // convert to relative position if (cur_param < 0) // Target is above entity set_up_cardinal_movement(ent, Direction.Up, -cur_param, false); else // Target is below entity set_up_cardinal_movement(ent, Direction.Down, cur_param, false); break; case MovestringCommand.Up: set_up_cardinal_movement(ent, Direction.Up, cur_param, movestring.tile_movement); break; case MovestringCommand.Down: set_up_cardinal_movement(ent, Direction.Down, cur_param, movestring.tile_movement); break; case MovestringCommand.Left: set_up_cardinal_movement(ent, Direction.Left, cur_param, movestring.tile_movement); break; case MovestringCommand.Right: set_up_cardinal_movement(ent, Direction.Right, cur_param, movestring.tile_movement); break; } } if (movestring.movement_left > 0) { if (movestring.movement_left <= time_left) { // Can move farther than is needed in the remaining time ent.velocity += Utility.velocity_from_direction(ent.movement_direction, movestring.movement_left / 100f, ((float)movestring.movement_left) / ent.speed); time_left -= movestring.movement_left; movestring.movement_left = Int32.MinValue; return time_left; } else { // Entity will spend the entire period moving ent.velocity += Utility.velocity_from_direction(ent.movement_direction, ent.speed, 100f); movestring.movement_left -= time_left; return 0; } } } return 0; }
// Moves the entity in accordance with the player's inputs. This works like VERGE player input // EXCEPT that, when diagonals are enabled, it moves the player at the same speed overall // (unlike VERGE, which moved them both vertically and horizontally as fast as if they were // moving in that direction alone) public static int vergestyle_player_movement_handler(Entity ent, Object state, ref EntityMovementData data) { int x = 0, y = 0; float factor; Vector2 ofs; VERGEMap map; Point pt; Rectangle box; ent.velocity = ent.acceleration = Vector2.Zero; ent.acceleration = Vector2.Zero; if (data.first_call) { if (VERGEGame.game.dir.left.down) x--; if (VERGEGame.game.dir.right.down) x++; if (VERGEGame.game.dir.up.down) y--; if (VERGEGame.game.dir.down.down) y++; } else if (data.collided) { // Hacky sliding code. Clean this shit up sometime! map = VERGEGame.game.map; x = Math.Sign(data.attempted_path.X); y = Math.Sign(data.attempted_path.Y); if (data.obstructed_by_entity) { box = data.collided_entity.hitbox; if (box.Top < ent.hitbox.Bottom && box.Bottom > ent.hitbox.Top) x = 0; if (box.Left < ent.hitbox.Right && box.Right > ent.hitbox.Left) y = 0; if (x != 0 && y != 0) { x = 0; y = 0; } } if (x == 0 && y != 0) { pt = new Point(ent.hitbox.X + 1, ent.hitbox.Y + y + (1 + y) * (ent.hitbox.Height - 1) / 2); for (int i = 0; i < ent.hitbox.Width - 2; i++) { // first check all but the ends if (map.obs_at_pixel(pt.X, pt.Y)) { y = 0; break; } pt.X++; } if (y != 0) { if (map.obs_at_pixel(ent.hitbox.Left, pt.Y)) { if (!map.obs_at_pixel(ent.hitbox.Right - 1, pt.Y)) x = 1; else y = 0; } else if (map.obs_at_pixel(ent.hitbox.Right - 1, pt.Y)) { if (!map.obs_at_pixel(ent.hitbox.Left, pt.Y)) x = -1; else y = 0; } if (x != 0) { pt.X = ent.hitbox.X + x + (1 + x) * (ent.hitbox.Width - 1) / 2; for (int i = 0; i < ent.hitbox.Height + 1; i++) { if (map.obs_at_pixel(pt.X, pt.Y)) { x = 0; y = 0; break; } pt.Y -= y; } } } } else if (y == 0 && x != 0) { pt = new Point(ent.hitbox.X + x + (1 + x) * (ent.hitbox.Width - 1) / 2, ent.hitbox.Y + 1); for (int i = 0; i < ent.hitbox.Height - 2; i++) { // first check all but the ends if (map.obs_at_pixel(pt.X, pt.Y)) { x = 0; break; } pt.Y++; } if (x != 0) { if (map.obs_at_pixel(pt.X, ent.hitbox.Top)) { if (!map.obs_at_pixel(pt.X, ent.hitbox.Bottom - 1)) y = 1; else x = 0; } else if (map.obs_at_pixel(pt.X, ent.hitbox.Bottom - 1)) { if (!map.obs_at_pixel(pt.X, ent.hitbox.Top)) y = -1; else x = 0; } if (y != 0) { pt.Y = ent.hitbox.Y + y + (1 + y) * (ent.hitbox.Height - 1) / 2; for (int i = 0; i < ent.hitbox.Width + 1; i++) { if (map.obs_at_pixel(pt.X, pt.Y)) { x = 0; y = 0; break; } pt.X -= x; } } } } else if (x != 0 && y != 0) { pt = new Point(ent.hitbox.X + x + (1 + x) * (ent.hitbox.Width - 1) / 2, ent.hitbox.Y + (1 + y) * (ent.hitbox.Height - 1) / 2); for (int i=0; i < ent.hitbox.Height; i++) { if (map.obs_at_pixel(pt.X, pt.Y)) { x = 0; break; } pt.Y -= y; } pt = new Point(ent.hitbox.X + (1 + x) * (ent.hitbox.Width - 1) / 2, ent.hitbox.Y + y + (1 + y) * (ent.hitbox.Height - 1) / 2); for (int i = 0; i < ent.hitbox.Width; i++) { if (map.obs_at_pixel(pt.X, pt.Y)) { y = 0; break; } pt.X -= x; } if (x != 0 && y != 0 && map.obs_at_pixel(ent.hitbox.X + x + (1 + x) * (ent.hitbox.Width - 1) / 2, ent.hitbox.Y + y + (1 + y) * (ent.hitbox.Height - 1) / 2)) { if (Math.Abs(data.attempted_path.X) > Math.Abs(data.attempted_path.Y)) y = 0; else if (Math.Abs(data.attempted_path.X) < Math.Abs(data.attempted_path.Y)) x = 0; else {x = 0; y = 0; } } if (x == 0 && y == 0) return 0; } if (x != 0 && y != 0) { // adjust alignment for diagonal movement ofs = new Vector2( Math.Abs((float) (ent.hitbox.X + (1 + x) / 2) - ent.exact_x), Math.Abs((float) (ent.hitbox.Y + (1 + y) / 2) - ent.exact_y)); if (ofs.X < ofs.Y) ent.exact_y += y * (ofs.Y - ofs.X); else if (ofs.X > ofs.Y) ent.exact_x += x * (ofs.X - ofs.Y); } } if (x == 0 && y == 0) { if (ent.moving) ent.set_walk_state(false); } else { ent.velocity.X = (float)x; ent.velocity.Y = (float)y; if (!ent.moving) ent.set_walk_state(true); factor = ent.speed / 100f; ent.movement_direction = Utility.direction_from_signs(x, y, true); if (!data.collided) { // Moving normally ent.facing = Utility.direction_from_signs(x, y, false); } if (Math.Abs(x) + Math.Abs(y) == 2) factor *= Utility.INV_SQRT2; // diagonal movement ent.velocity *= factor; if (data.collided) { // if sliding, move at most one pixel in this handler iteration // figure out how much time it will take to move one pixel factor = data.time - ent.speed; if (Math.Abs(x) + Math.Abs(y) == 2) factor *= Utility.SQRT2; return Math.Max((int)Math.Floor(factor), 0); } } return 0; }
// A general purpose VERGE-emulation handler that defers to various other handlers depending on // the entity's state. public static int omnibus_vergestyle_handler(Entity ent, Object state, ref EntityMovementData data) { if (ent == VERGEGame.game.player && VERGEGame.game.player_controllable) return vergestyle_player_movement_handler(ent, state, ref data); else return entity_movescript_handler(ent, state, ref data); }