License text

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER 
 * Copyright  2008, 2010 Oracle and/or its affiliates.  All rights reserved. 
 * Use is subject to license terms.
 * 
 * This file is available and licensed under the following license:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 * 
 *   * Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer. 
 *
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *   * Neither the name of Oracle Corporation nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software 
 *     without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

    

package dukeman;

import javafx.scene.*;
import javafx.scene.image.*;

//@author Pavel Porvatov

public def STATE_HUNTING = 0;
public def STATE_SAFE = 1;
public def STATE_DEFEAT = 2;

public def TYPE_0 = 0;
public def TYPE_1 = 1;
public def TYPE_2 = 2;
public def TYPE_3 = 3;

def MAX_MOVE_COUNT = 100;

def ENEMY_AGGRESSION = [9, 7, 5, 3];            // Aggression level of enemies

public class Enemy extends CustomNode {
    public-read var state: Integer;

    public-init var type: Integer;

    var animationIndex: Integer;
    
    var speed: Integer;

    var safetyTime: Duration;

    // > 0: The enemy is parked in enemy home
    // = 0: The enemy is unparking
    // < 0: The enemy is outside of enemy home
    var parkingTime: Duration;

    // This count increases after every move() invocation
    // It is used for accurate speed calculation
    var moveCount;
    
    def direction = Direction { };
    
    def image = ImageView { };

    var homeEntrance: Coord;
    
    public function initialize(level: LevelData): Void {
        state = STATE_HUNTING;

        homeEntrance = Coord {
            x: level.enemyHome.x * Config.CELL_SIZE + Config.CELL_SIZE * 7 / 2
            y: (level.enemyHome.y - 1) * Config.CELL_SIZE
        }

        if (type == TYPE_0) {
            parkingTime = -1s;

            translateX = homeEntrance.x;
            translateY = homeEntrance.y;

            direction.offsetX = Utils.random(2) * 2 - 1;
            direction.offsetY = 0;
        } else {
            if (type == TYPE_2) {
                parkingTime = 0s;
            } else if (type == TYPE_1) {
                parkingTime = 4s;
            } else {
                parkingTime = 8s;
            }

            translateX = homeEntrance.x + Config.CELL_SIZE * (type - 2) * 2;
            translateY = homeEntrance.y + 3 * Config.CELL_SIZE;

            direction.offsetX = 0;
            direction.offsetY = 1;
        }

        // Calculate speed. In 20 level the fastest enemy has speed DUKE_SPEED * 1.2
        var coeff = Main.mainFrame.state - type;

        if (coeff < 0) {
            coeff = 0;
        }

        if (coeff > 20) {
            coeff = 20;
        }

        speed = (Config.DUKE_SPEED * MAX_MOVE_COUNT *
            (1 + 1.0 * coeff / MAX_MOVE_COUNT)) as Integer;

        prepareImage();

        transforms = [
            Utils.OFFSET_TRANSFORM
        ];
    }

    public function setState(state: Integer): Void {
        if (state == STATE_SAFE) {
            if (this.state == STATE_DEFEAT) {
                return
            }

            safetyTime = 8s;
        }

        this.state = state;
    }

    public function move(level: LevelData, duke: Duke) {
        // Move the enemy
        var moveLength: Integer = (speed * (moveCount + 1) / MAX_MOVE_COUNT as Integer) -
            (speed * moveCount / MAX_MOVE_COUNT as Integer);

        if (state == STATE_SAFE) {
            moveLength /= 2;
        }

        if (moveCount < MAX_MOVE_COUNT - 1) {
            moveCount++
        } else {
            moveCount = 0
        }

        for (i in [0..<moveLength]) {
            if (parkingTime > 0s) {
                // The enemy is parked
                translateY += direction.offsetY;

                if (translateY == (level.enemyHome.y + 1.5) * Config.CELL_SIZE or
                        translateY == (level.enemyHome.y + 2.5) * Config.CELL_SIZE) {
                    direction.offsetY = -direction.offsetY;

                    directionChanged();
                }
            } else if (parkingTime == 0s) {
                // The enemy is unparking
                def offsetX = Utils.sign(homeEntrance.x - translateX);

                if (offsetX == 0) {
                    translateY--;

                    if (translateY == homeEntrance.y) {
                        parkingTime = -1s;

                        direction.offsetX = Utils.random(2) * 2 - 1;
                        direction.offsetY = 0;

                        directionChanged();
                    }
                } else {
                    translateX += offsetX;
                }
            } else if (state == STATE_DEFEAT and translateX == homeEntrance.x and
                    translateY >= homeEntrance.y and
                    translateY < homeEntrance.y + Config.CELL_SIZE * 3) {
                // Eated enemy is entering home
                translateY++;

                if (translateY == homeEntrance.y + Config.CELL_SIZE * 3) {
                    state = STATE_HUNTING;

                    parkingTime = 0s;
                }
            } else {
                // The enemy is outside of enemy home
                if (translateX mod Config.CELL_SIZE == 0 and
                    translateY mod Config.CELL_SIZE == 0) {
                    // Collect all possible directions except reverse direction
                    var possibleDirections: Direction[];

                    for (d in Direction.ALL_DIRECTIONS) {
                        if ((direction.offsetX != -d.offsetX or
                                direction.offsetY != -d.offsetY) and
                                level.canMove(this, d)) {
                            insert d into possibleDirections;
                        }
                    }

                    if (sizeof possibleDirections == 0) {
                        // Revert direction
                        direction.offsetX = -direction.offsetX;
                        direction.offsetY = -direction.offsetY;

                        directionChanged();
                    } else {
                        var newDirection: Direction;

                        if ((state == STATE_HUNTING and Utils.random(10) < ENEMY_AGGRESSION[type]) or
                                (state == STATE_DEFEAT and Utils.random(10) < 9)) {
                            // STATE_HUNTING: The enemy selects the best direction to the duke
                            // STATE_EATED: The enemy selects the best direction to home
                            var destX;
                            var destY;

                            if (state == STATE_HUNTING) {
                                destX = duke.translateX;
                                destY = duke.translateY;
                            } else {
                                destX = level.enemyHome.x * Config.CELL_SIZE +
                                     Config.CELL_SIZE * 7 / 2;
                                destY = (level.enemyHome.y - 1) * Config.CELL_SIZE;
                            }

                            for (d in possibleDirections) {
                                // Enemies with odd type have Y-axis priority,
                                // others - X-axis priority
                                if (d.offsetX == 0 and d.offsetY ==
                                        Utils.sign(destY - translateY)) {
                                    if (newDirection == null or type mod 2 != 0) {
                                        newDirection = d;
                                    }
                                }

                                if (d.offsetY == 0 and d.offsetX ==
                                        Utils.sign(destX - translateX)) {
                                    if (newDirection == null or type mod 2 == 0) {
                                        newDirection = d;
                                    }
                                }
                            }
                        }

                        if (newDirection == null) {
                            // There is no available good direction, selects random one
                            newDirection = possibleDirections[Utils.random(
                                    sizeof possibleDirections)];
                        }

                        direction.offsetX = newDirection.offsetX;
                        direction.offsetY = newDirection.offsetY;

                        directionChanged();
                    }
                }

                level.move(this, direction);
            }
        }

        // Update parkingTime
        if (parkingTime > 0s) {
            parkingTime -= Config.ANIMATION_TIME;

            if (parkingTime < 0s) {
                parkingTime = 0s
            }
        }

        // Update state
        if (state == STATE_SAFE) {
            safetyTime -= Config.ANIMATION_TIME;

            if (safetyTime < 0s) {
                state = STATE_HUNTING
            }
        }

        if (animationIndex >= 11) {
            animationIndex = 0;
        } else {
            animationIndex++;
        }

        prepareImage();
    }

    function prepareImage() {
        var index;

        if (state == STATE_DEFEAT) {
            index = Config.IMAGE_DEFEAT_ENEMY0 + animationIndex / 3;
        } else if (state == STATE_SAFE and (safetyTime > 3s or
                ((safetyTime.toMillis() / 200) as Integer) mod 2 == 0)) {
            index = Config.IMAGE_SAFE_ENEMY0 + animationIndex / 3;
        } else {
            index = Config.IMAGE_NORMAL_ENEMY0 + animationIndex / 3;
        }

        image.image = Config.images[index]
    }

    function directionChanged() {
        if (direction.offsetX > 0) {
            transforms = [
                Utils.OFFSET_TRANSFORM
            ];
        }

        if (direction.offsetX < 0) {
            transforms = [
                Utils.FLIP_TRANSFORM,
                Utils.BIG_OFFSET_TRANSFORM
            ];
        }
    }
    
    override public function create(): Node {
        image
    }
}