// Copyright (c) 2023 Alex Diener. All rights reserved. #include "PROJECT_NAME/Atoms.h" #include "PROJECT_NAME/EntityComponent_pickup.h" #include "PROJECT_NAME/EntityPlayer.h" #include "PROJECT_NAME/GameState.h" #include "PROJECT_NAME/RoomState.h" #include "PROJECT_NAME/SharedDefinitions.h" #include "PROJECT_NAME/Sprites.h" #include "PROJECT_NAME/Utilities.h" #define stemobject_implementation EntityPlayer stemobject_vtable_begin(); stemobject_vtable_entry(createWithEncodedState); stemobject_vtable_entry(initWithEncodedState); stemobject_vtable_entry(copy); stemobject_vtable_entry(initCopy); stemobject_vtable_entry(dispose); stemobject_vtable_entry(getComponent); stemobject_vtable_entry(preadvance); stemobject_vtable_entry(advanceTurn); stemobject_vtable_entry(isEqual); stemobject_vtable_entry(enumerateProperties); stemobject_vtable_entry(undoActionPerformed); stemobject_vtable_end(); EntityPlayer * EntityPlayer_create(Vector2i position, struct RoomState * roomState) { stemobject_create_implementation(init, position, roomState) } static bool collisionCallback(struct GameEntity * collidingEntity, Vector2i fromDirection, void * context) { EntityPlayer * self = context; if (self->dead) { return false; } EntityComponent_attack * attackComponent = call_virtual(getComponent, collidingEntity, COMPONENT_ATTACK); if (attackComponent != NULL && (attackComponent->type & ATTACK_HURT_PLAYER)) { self->dead = true; self->spriteComponent.spriteID = SPRITE_NONE; EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_killed, self); if (attackComponent->type & ATTACK_OVERRIDE_COLLISION) { return false; } } return true; } static void updateSprite(EntityPlayer * self) { if (self->dead) { self->spriteComponent.spriteID = SPRITE_NONE; } else { switch (self->facingComponent.facing) { case FACING_NORTH: self->spriteComponent.spriteID = SPRITE_ID_PLAYER_NORTH; break; case FACING_EAST: self->spriteComponent.spriteID = SPRITE_ID_PLAYER_EAST; break; case FACING_SOUTH: self->spriteComponent.spriteID = SPRITE_ID_PLAYER_SOUTH; break; case FACING_WEST: self->spriteComponent.spriteID = SPRITE_ID_PLAYER_WEST; break; } } } static void initComponents(EntityPlayer * self, Vector2i position) { stemobject_assign_vtable(self->attackComponent, EntityComponent_attack); stemobject_assign_vtable(self->collidableComponent, EntityComponent_collidable); stemobject_assign_vtable(self->facingComponent, EntityComponent_facing); stemobject_assign_vtable(self->movementComponent, EntityComponent_movement); stemobject_assign_vtable(self->positionComponent, EntityComponent_position); stemobject_assign_vtable(self->spriteComponent, EntityComponent_sprite); stemobject_assign_vtable(self->steeringComponent, EntityComponent_playerSteering); stemobject_assign_vtable(self->pusherComponent, EntityComponent_pusher); EntityComponent_attack_init(&self->attackComponent, ATTACK_DESTROY_WALL | ATTACK_HURT_ENEMY); EntityComponent_collidable_init(&self->collidableComponent, collisionCallback, self); EntityComponent_facing_init(&self->facingComponent, FACING_SOUTH); EntityComponent_movement_init(&self->movementComponent, true); EntityComponent_position_init(&self->positionComponent, position); EntityComponent_sprite_init(&self->spriteComponent, SPRITE_ID_PLAYER_SOUTH, DRAW_LAYER_ENTITY, 1); EntityComponent_playerSteering_init(&self->steeringComponent); EntityComponent_pusher_init(&self->pusherComponent); } bool EntityPlayer_init(EntityPlayer * self, Vector2i position, struct RoomState * roomState) { call_super(init, self, roomState); self->dead = false; initComponents(self, position); updateSprite(self); return true; } EntityPlayer * EntityPlayer_createWithEncodedState(struct memreadContext * memreadContext, struct RoomState * roomState) { GameEntity_createWithEncodedState_implementation(); } void EntityPlayer_initWithEncodedState(EntityPlayer * self, struct memreadContext * memreadContext, struct RoomState * roomState) { initComponents(self, VECTOR2i_ZERO); call_super_virtual(initWithEncodedState, self, memreadContext, roomState); self->positionComponent.lastPosition = self->positionComponent.position; updateSprite(self); } EntityPlayer * EntityPlayer_copy(EntityPlayer * self, struct RoomState * roomState) { stemobject_copy_implementation(initCopy, roomState) } void EntityPlayer_initCopy(EntityPlayer * self, compat_type(EntityPlayer *) originalUntyped, struct RoomState * roomState) { call_super_virtual(initCopy, self, originalUntyped, roomState); EntityPlayer * original = originalUntyped; self->dead = original->dead; initComponents(self, original->positionComponent.position); call_virtual(initCopy, &self->attackComponent, &original->attackComponent); call_virtual(initCopy, &self->collidableComponent, &original->collidableComponent); self->collidableComponent.callbackContext = self; call_virtual(initCopy, &self->facingComponent, &original->facingComponent); call_virtual(initCopy, &self->movementComponent, &original->movementComponent); call_virtual(initCopy, &self->positionComponent, &original->positionComponent); call_virtual(initCopy, &self->spriteComponent, &original->spriteComponent); call_virtual(initCopy, &self->steeringComponent, &original->steeringComponent); call_virtual(initCopy, &self->pusherComponent, &original->pusherComponent); updateSprite(self); } void EntityPlayer_dispose(EntityPlayer * self) { call_super_virtual(dispose, self); } compat_type(GameEntityComponent *) EntityPlayer_getComponent(EntityPlayer * self, ComponentType type) { if (self->dead && type != COMPONENT_POSITION) { return NULL; } switch (type) { case COMPONENT_ATTACK: return &self->attackComponent; case COMPONENT_COLLIDABLE: return &self->collidableComponent; case COMPONENT_FACING: return &self->facingComponent; case COMPONENT_MOVEMENT: return &self->movementComponent; case COMPONENT_POSITION: return &self->positionComponent; case COMPONENT_PUSHER: return &self->pusherComponent; case COMPONENT_SPRITE: return &self->spriteComponent; case COMPONENT_STEERING: return &self->steeringComponent; default: break; } return NULL; } void EntityPlayer_preadvance(EntityPlayer * self) { call_virtual(update, &self->positionComponent, (GameEntity *) self); } void EntityPlayer_advanceTurn(EntityPlayer * self) { if (self->dead) { return; } call_virtual(update, &self->movementComponent, (GameEntity *) self); switch (self->movementComponent.lastMoveResult) { case MOVE_RESULT_IDLE: break; case MOVE_RESULT_MOVED: EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_moved, self); break; case MOVE_RESULT_REFUSED: EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_move_failed, self); break; case MOVE_RESULT_BLOCKED: EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_player_bumped_wall, self); break; } unsigned int entityCount = RoomState_getEntityCountAtPosition(self->roomState, self->positionComponent.position); for (unsigned int entityIndex = 0; entityIndex < entityCount; entityIndex++) { GameEntity * entity = RoomState_getEntityAtPositionAtIndex(self->roomState, self->positionComponent.position, entityIndex); if (!entity->markedForRemoval) { EntityComponent_pickup * pickupComponent = call_virtual(getComponent, entity, COMPONENT_PICKUP); if (pickupComponent != NULL) { EventDispatcher_dispatchEvent(self->roomState->eventDispatcher, ATOM_event_pickup_collected, entity); entity->markedForRemoval = true; } } } } bool EntityPlayer_isEqual(EntityPlayer * self, EntityPlayer * compare) { return self->facingComponent.facing == compare->facingComponent.facing && self->positionComponent.position.x == compare->positionComponent.position.x && self->positionComponent.position.y == compare->positionComponent.position.y && self->dead == compare->dead; } void EntityPlayer_enumerateProperties(EntityPlayer * self, bool transient, GameEntity_enumeratePropertiesCallback callback, void * context) { call_super_virtual(enumerateProperties, self, transient, callback, context); callback(&self->positionComponent.position, PROPERTY_TYPE_Vector2i_int8_t, context); callback(&self->facingComponent.facing, PROPERTY_TYPE_uint8_t, context); callback(&self->dead, PROPERTY_TYPE_bool, context); } void EntityPlayer_undoActionPerformed(EntityPlayer * self) { updateSprite(self); } void EntityPlayer_setFacing(EntityPlayer * self, FacingDirection facing) { self->facingComponent.facing = facing; updateSprite(self); } static FacingDirection moveInputToFacingDirection(MoveInput_axis axis, MoveInput_direction direction) { if (axis == MOVE_AXIS_X) { return direction == MOVE_DIRECTION_POSITIVE ? FACING_EAST : FACING_WEST; } return direction == MOVE_DIRECTION_POSITIVE ? FACING_SOUTH : FACING_NORTH; } bool EntityPlayer_startMoveInput(EntityPlayer * self, MoveInput_axis axis, MoveInput_direction direction, bool repeat) { bool changed = false; if (!repeat) { changed = call_virtual(startMoveInput, &self->steeringComponent, axis, direction); } if (changed || call_virtual(isTurnAdvancingInput, &self->steeringComponent, axis, direction)) { EntityPlayer_setFacing(self, moveInputToFacingDirection(axis, direction)); GameState_advanceTurn(self->roomState->gameState); return true; } return changed; } bool EntityPlayer_stopMoveInput(EntityPlayer * self, MoveInput_axis axis, MoveInput_direction direction) { return call_virtual(stopMoveInput, &self->steeringComponent, axis, direction); } bool EntityPlayer_startActionInput(EntityPlayer * self, ActionInput_action action, bool repeat) { bool changed = false; if (!repeat) { changed = call_virtual(startActionInput, &self->steeringComponent, action); } switch (action) { case ACTION_WAIT: call_virtual(cancelAllMoveInputs, &self->steeringComponent); GameState_advanceTurn(self->roomState->gameState); return true; } return changed; } bool EntityPlayer_stopActionInput(EntityPlayer * self, ActionInput_action action) { return call_virtual(stopActionInput, &self->steeringComponent, action); } void EntityPlayer_offsetLocalPosition(EntityPlayer * self, Vector2i offset) { self->positionComponent.position.x += offset.x; self->positionComponent.position.y += offset.y; self->positionComponent.lastPosition.x += offset.x; self->positionComponent.lastPosition.y += offset.y; }