Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #import "DTEnemyMovement.h"
- #import "DTTowerFloor.h"
- /*
- Each tick something will happen based on the state, or the state will be processed elsewhere
- Towers and Floors both may contain enemies, though they will always be on a floor unless moving between floors
- To update the movement, a method converts the current state to a string, and gets the method name to call from a dictionary
- This works like a decision table and removes the need for a switch statement
- Movement can only be up or down, left or right, no combination of them
- */
- //Two options for the decision table, either access the dictionary directly with 0-x, the enum values, or make strings for their names
- //the advantage of strings is that it is more extensible, and the position in the enum doesnt matter
- NSString* const kEnemyMovementStateJustSpawned = @"enemyMovementStateJustSpawned";
- NSString* const kEnemyMovementStateIdle = @"enemyMovementStateIdle";
- NSString* const kEnemyMovementStateNeedsMoving = @"enemyMovementStateNeedsMoving";
- NSString* const kEnemyMovementStateToFloor = @"enemyMovementStateToFloor";
- NSString *const kEnemyMovementStateAtDestinationFloor = @"enemyMovementStateAtDestinationFloor";
- NSString* const kEnemyMovementStateToFloorExit = @"enemyMovementStateToFloorExit";
- //NSString* const kEnemyMovementStateAtFloorExit = @"enemyMovementStateAtFloorExit"; //tower handles
- NSString* const kEnemyMovementStateToAttackWalls = @"enemyMovementStateToAttackWalls";
- //NSString* const kEnemyMovementStateAtAttackWalls = @"enemyMovementStateAtAttackWalls"; //floor handles
- NSString* const kEnemyMovementStateToAttackFloor = @"enemyMovementStateToAttackFloor";
- //NSString* const kEnemyMovementStateAtAttackFloor = @"enemyMovementStateAtAttackFloor"; //floor handles
- NSString* const kEnemyMovementStateToAttackRoom = @"enemyMovementStateToAttackRoom";
- //NSString* const kEnemyMovementStateAtAttackRoom = @"enemyMovementStateAtAttackRoom"; //floor handles
- //NSString* const kEnemyMovementStateStunned = @"enemyMovementStateStunned"; //not yet used
- @implementation DTEnemyMovement {
- NSMutableArray *_floorList;
- NSMutableArray *_floorRange; //the range will be set based on the initial floor
- int _startingFloor;
- NSDictionary *_decisionTable;
- }
- -(id) initWithWorldSizeForCharacter:(CGSize)worldSize andStartingFloor:(int)startingFloor {
- self = [super initWithWorldSize:worldSize];
- if (self) {
- _floorList = [[NSMutableArray alloc]init];
- _floorRange = [[NSMutableArray alloc]init];
- _startingFloor = startingFloor;
- _decisionTable = [[NSDictionary alloc]init];
- [self setupDecisionTable];
- }
- return self;
- }
- -(void) setupDecisionTable {
- //the string objects are the names of methods in the class
- _decisionTable = @{kEnemyMovementStateJustSpawned: @"doEnemyJustSpawned",
- kEnemyMovementStateIdle: @"doEnemyIdle",
- kEnemyMovementStateNeedsMoving: @"doEnemyNeedsMoving",
- kEnemyMovementStateToFloorExit: @"doFloorMovement",
- kEnemyMovementStateToFloor: @"doVerticalMovement",
- kEnemyMovementStateAtDestinationFloor: @"doEnemyAtDestinationFloor",
- kEnemyMovementStateToAttackWalls: @"doFloorMovement",
- kEnemyMovementStateToAttackFloor: @"doFloorMovement",
- kEnemyMovementStateToAttackRoom: @"doFloorMovement"
- };
- }
- -(NSString *) stringForState:(EnemyMovementState)state {
- switch (state) {
- case EnemyMovementStateJustSpawned:
- return kEnemyMovementStateJustSpawned;
- case EnemyMovementStateIdle:
- return kEnemyMovementStateIdle;
- case EnemyMovementStateNeedsMoving:
- return kEnemyMovementStateNeedsMoving;
- case EnemyMovementStateToFloor:
- return kEnemyMovementStateToFloor;
- case EnemyMovementStateAtDestinationFloor:
- return kEnemyMovementStateAtDestinationFloor;
- case EnemyMovementStateToFloorExit:
- return kEnemyMovementStateToFloorExit;
- case EnemyMovementStateAtFloorExit:
- return nil;
- case EnemyMovementStateToAttackWalls:
- return kEnemyMovementStateToAttackWalls;
- case EnemyMovementStateAtAttackWalls:
- return nil;
- case EnemyMovementStateToAttackFloor:
- return kEnemyMovementStateToAttackFloor;
- case EnemyMovementStateAtAttackFloor:
- return nil;
- case EnemyMovementStateToAttackRoom:
- return kEnemyMovementStateToAttackRoom;
- case EnemyMovementStateAtAttackRoom:
- return nil;
- default:
- break;
- }
- return nil;
- }
- #pragma mark - Update Loop
- -(void) doMovement {
- //the selector is formed from a string inside the decision table dictionary
- SEL methodToCallName = NSSelectorFromString([_decisionTable objectForKey:[self stringForState:self.state]]);
- if (methodToCallName) {
- IMP functionPointer = [self methodForSelector:methodToCallName];
- void (*methodToCall)(id, SEL) = (void *)functionPointer;
- methodToCall(self, methodToCallName);
- }
- }
- -(void) doEnemyJustSpawned {
- if ([self checkFloorsForJobs]) {
- self.state = EnemyMovementStateNeedsMoving;
- } else {
- self.state = EnemyMovementStateIdle;
- }
- }
- -(void) doEnemyIdle {
- if ([self checkFloorsForJobs]) {
- self.state = EnemyMovementStateNeedsMoving;
- } else {
- [self doIdleMovement];
- }
- }
- -(void) doEnemyNeedsMoving {
- [self calculateFloorExitPositionByFloor];
- self.state = EnemyMovementStateToFloorExit;
- }
- -(void) doEnemyAtDestinationFloor {
- BOOL foundADestination = NO;
- for (DTTowerFloor *floor in _floorList) {
- if (floor.floorNumber == self.currentFloor) {
- //attack room first, then walls, then bottom
- if (floor.floorBuildState & FloorHasRoom) {
- [self moveToAttackRoom];
- foundADestination = YES;
- } else if (floor.floorBuildState & FloorHasWalls) {
- [self moveToAttackWalls];
- foundADestination = YES;
- } else if (floor.floorBuildState & FloorHasBottom) {
- [self moveToAttackBottom];
- foundADestination = YES;
- }
- }
- }
- if (!foundADestination) {
- self.state = EnemyMovementStateIdle;
- }
- }
- -(void) moveToAttackBottom {
- [self pickRandomDestinationOnCurrentFloor];
- self.state = EnemyMovementStateToAttackFloor;
- }
- -(void) moveToAttackRoom {
- [self pickRandomDestinationOnCurrentFloor];
- self.state = EnemyMovementStateToAttackRoom;
- }
- -(void) moveToAttackWalls {
- //decide which wall to move to, the nearest one
- int positiveOrNegative = 0;
- if (self.currentPosition.x >= 0) {
- positiveOrNegative = 1;
- } else {
- positiveOrNegative = -1;
- }
- //move to the left or right wall
- self.destinationPosition = CGPointMake(([self worldSize].width / 2.1) * positiveOrNegative, self.currentPosition.y);
- self.state = EnemyMovementStateToAttackWalls;
- }
- -(BOOL) checkFloorsForJobs {
- NSMutableSet *floorsInRange = [[NSMutableSet alloc]init];
- for (DTTowerFloor *floor in _floorList) {
- if (floor.floorNumber == _startingFloor || floor.floorNumber == _startingFloor - 1 || floor.floorNumber == _startingFloor + 1) {
- if (floor.floorBuildState & FloorHasWalls) {
- [floorsInRange addObject:floor];
- } else if (floor.floorBuildState & FloorHasBottom) {
- [floorsInRange addObject:floor];
- } else if (floor.floorBuildState & FloorHasRoomUpgrade) {
- [floorsInRange addObject:floor];
- }
- }
- }
- if (floorsInRange.count > 0) {
- NSMutableArray *tempArray = [[NSMutableArray alloc]init];
- for (DTTowerFloor *floor in floorsInRange) {
- [tempArray addObject:floor];
- }
- self.destinationFloor = [self closestFloor:tempArray];
- return YES;
- }
- return NO;
- }
- #pragma mark - Overrides
- -(void) arriveAtDestinationFloor {
- self.currentPosition = self.destinationPosition;
- self.currentFloor = self.destinationFloor;
- self.state = EnemyMovementStateAtDestinationFloor;
- //not sure if this conditional is needed but it is good for debugging
- if (self.state == EnemyMovementStateToFloor) {
- self.state = EnemyMovementStateAtDestinationFloor;
- }
- }
- -(void) doFloorMovement {
- //once close enough to the destination, jump to the destination
- if ([self isAtFloorDestinationPosition]) {
- self.currentPosition = self.destinationPosition;
- if (self.state == EnemyMovementStateToFloorExit) {
- self.state = EnemyMovementStateAtFloorExit;
- } else if (self.state == EnemyMovementStateToAttackWalls) {
- self.state = EnemyMovementStateAtAttackWalls;
- } else if (self.state == EnemyMovementStateToAttackFloor) {
- self.state = EnemyMovementStateAtAttackFloor;
- } else if (self.state == EnemyMovementStateToAttackRoom) {
- self.state = EnemyMovementStateAtAttackRoom;
- }
- } else {
- if ([self shouldMoveRight]) {
- [self moveRight];
- } else {
- [self moveLeft];
- }
- }
- }
- #pragma mark - Floor List
- -(void) acceptFloorList:(NSMutableArray *)floorList {
- _floorList = floorList;
- }
- #pragma mark - NSCoding methods
- -(id) initWithCoder:(NSCoder *)aDecoder {
- self = [super initWithCoder:aDecoder];
- if (self) {
- _state = [aDecoder decodeIntegerForKey:@"enemyMovementState"];
- _floorList = [aDecoder decodeObjectForKey:@"floorList"];
- _startingFloor = [aDecoder decodeIntForKey:@"startingFloor"];
- _decisionTable = [aDecoder decodeObjectForKey:@"decisionTable"];
- }
- return self;
- }
- -(void) encodeWithCoder:(NSCoder *)aCoder {
- [super encodeWithCoder:aCoder];
- [aCoder encodeInteger:self.state forKey:@"enemyMovementState"];
- [aCoder encodeObject:_floorList forKey:@"floorList"];
- [aCoder encodeInt:_startingFloor forKey:@"startingFloor"];
- [aCoder encodeObject:_decisionTable forKey:@"decisionTable"];
- }
- @end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement