Пример #1
0
    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;
    }
Пример #2
0
    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;
    }
Пример #3
0
        // 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;
            }
        }
Пример #4
0
        // 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;
            }
        }
Пример #5
0
        public virtual void try_to_move(ref EntityMovementData data)
        {
            try_to_move_obs(ref data);
            try_to_move_ent(ref data);

            return;
        }
Пример #6
0
        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;
        }
Пример #7
0
 public DungeonBoss(EntityCombatAI combatAI, EntityTaskAI taskAI, EntityMovementData movementData = default(EntityMovementData),
                    string name = "un-named_entity", bool isFixed = false)
     : base(combatAI, taskAI, movementData, name, isFixed)
 {
 }
Пример #8
0
 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);
 }
Пример #9
0
        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;
        }
Пример #10
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;
        }
Пример #11
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);
 }