/* Copyright (C) 2006 Parallel Realities This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USAf. */ #include "player.h" void removeSpecialWeapons() { bool remove; for (int i = 0 ; i < 4 ; i++) { if (game->weapon[i] == NULL) { continue; } remove = false; switch (game->weapon[i]->definition->type) { case WEP_ICE_GUN: case WEP_FLAMETHROWER: case WEP_DESIMILATOR: remove = true; break; default: remove = false; break; } if (remove) { debug(("removeSpecialWeapons - Removing '%s'\n", game->weapon[i]->getName())); if (game->selectedWeapon == game->weapon[i]) { game->selectedWeapon = game->weapon[0]; } game->weapon[i]->setOwner(NULL); game->weapon[i] = NULL; } } } WeaponDef *getRandomPlayerAmmoType() { WeaponDef *rtn = NULL; if ((game->weapon[1] == NULL) && (game->weapon[2] == NULL)) { return NULL; } else if ((game->weapon[1] != NULL) && (game->weapon[2] != NULL)) { rtn = (WeaponDef*)game->weapon[1 + rand() % 2]->definition; } else if (game->weapon[1] == NULL) { rtn = (WeaponDef*)game->weapon[2]->definition; } else if (game->weapon[2] == NULL) { rtn = (WeaponDef*)game->weapon[1]->definition; } // Plasma rifles are very powerful... Don't drop them too often or it makes the game stupidly easy if ((rtn != NULL) && (rtn->type == WEP_PLASMA_RIFLE)) { if (mission->enemyLevel < 5) { if ((rand() % 3) != 0) { rtn = NULL; } } else { if ((rand() % 2) == 0) { rtn = NULL; } } } // don't drop desimilator ammo... if (rtn != NULL) { switch (rtn->type) { case WEP_DESIMILATOR: case WEP_ICE_GUN: case WEP_FLAMETHROWER: rtn = NULL; break; default: break; } } return rtn; } void bobStunnedTouch(Entity *other) { if (other == NULL) { return; } switch (other->entityType) { case ET_SUPPLY_CRATE: other->health = 0; break; case ET_BLOB: case ET_BIOMECH: other->velocity.x = self->velocity.x / 2; other->velocity.y = self->velocity.y / 2; other->velocity.z = self->velocity.z / 2; audio->playSound(SND_HIT, CH_ANY, camera->getSoundDistance(other->position)); ((Unit*)other)->helpless += 75; break; } } void blobWalkTurn() { } void resetPlayer() { player->helpless = 0; player->position = game->checkpoint; player->velocity.set(0, 0, 0); player->rotation.set(0, 0, 0); player->contentType = CONTENTS_SOLID; player->flags &= ~EF_BOUNCES; game->setMissionStatus(MISSION_INPROGRESS); } void calculatePlayerAimDirection() { Vector v, near, far, ray; if (game->autoLockTarget) { game->targetLock = player->position; float r = toRadians(player->rotation.x); game->targetLock.x += (sin(r) * 25); game->targetLock.y -= (cos(r) * 25); v = game->targetLock; v -= player->position; v.normalize(); game->targetLock += (v * 150); } else { graphics->convertToWorldCoords(graphics->screenMidX, graphics->screenMidY, &near, &far, &ray); game->targetLock = near; game->targetLock += (ray * 900); game->targetLock = bsp->getTraceLocation(camera->position, game->targetLock); game->targetLock = bbManager->getTraceLocation(player, camera->position, game->targetLock); game->aimColor.set(1.0, 1.0, 1.0, 0.2); if (bbManager->intersectedEntity != NULL) { switch (bbManager->intersectedEntity->entityType) { case ET_BLOB: game->aimColor.set(0.0, 1.0, 0.0, 0.75); break; case ET_BIOMECH: case ET_BOSS: game->aimColor.set(1.0, 0.0, 0.0, 0.75); break; case ET_SUPPLY_CRATE: game->aimColor.set(1.0, 0.0, 0.0, 0.75); break; case ET_WINDOW: game->aimColor.set(1.0, 0.0, 0.0, 0.75); break; case ET_STRUCTURE: if (!(bbManager->intersectedEntity->flags & EF_IMMORTAL)) { game->aimColor.set(1.0, 0.0, 0.0, 0.75); } break; } } } } void playerTargetEnemy() { Vector sight; float distance = 0; float targetDistance = (player->target == NULL) ? PLAYER_TARGET_RANGE : Math::getDistance(player->position, player->target->position); sight = player->position; sight.z += 3; for (Unit *unit = (Unit*)entityManager->enemyList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if ((!unit->canBeDamaged()) || (unit->health <= 0) || (!camera->frustum.containsSphere(unit->position, 6)) || (unit->flags & EF_NO_TARGET)) { continue; } distance = Math::getDistance(player->position, unit->position); if (distance < targetDistance) { if (unitCanSeeTarget(player, unit, sight)) { targetDistance = distance; player->target = unit; } } } for (Boss *boss = (Boss*)entityManager->bossList.getFirstElement() ; boss != NULL ; boss = (Boss*)boss->next) { if ((boss->health <= 0) || (boss->entityType == ET_NEUTRAL) || (!camera->frustum.containsSphere(boss->position, 6)) || (boss->flags & EF_VANISHED)) { continue; } distance = Math::getDistance(player->position, boss->position); if (distance < targetDistance) { if (unitCanSeeTarget(player, boss, sight)) { targetDistance = distance; player->target = boss; } } } } void playerCycleTargets() { float distance = 0; float targetDistance = PLAYER_TARGET_RANGE; float currentDistance = Math::getDistance(player->position, player->target->position); Entity *oldTarget = player->target; Vector sight = player->position; sight.z += 3; for (Unit *unit = (Unit*)entityManager->enemyList.getFirstElement() ; unit != NULL ; unit = (Unit*)unit->next) { if ((!unit->canBeDamaged()) || (unit->health <= 0) || (!camera->frustum.containsSphere(unit->position, 6)) || (unit->flags & EF_NO_TARGET)) { continue; } distance = Math::getDistance(player->position, unit->position); if ((distance < targetDistance) && (distance > currentDistance)) { if (unitCanSeeTarget(player, unit, sight)) { targetDistance = distance; player->target = unit; } } } for (Boss *boss = (Boss*)entityManager->bossList.getFirstElement() ; boss != NULL ; boss = (Boss*)boss->next) { if ((boss->health <= 0) || (boss->entityType == ET_NEUTRAL) || (!camera->frustum.containsSphere(boss->position, 6)) || (boss->flags & EF_VANISHED)) { continue; } distance = Math::getDistance(player->position, boss->position); if ((distance < targetDistance) && (distance > currentDistance)) { if (unitCanSeeTarget(player, boss, sight)) { targetDistance = distance; player->target = boss; } } } if (oldTarget == player->target) { playerTargetEnemy(); } } void playerSelectBestWeapon() { game->mustChangeWeapon = false; if ((game->weapon[1] != NULL) && (game->weapon[1]->currentAmmo > 0)) { game->selectedWeapon = game->weapon[1]; game->weaponIndex = 1; player->reload = 100; return; } else if ((game->weapon[2] != NULL)&& (game->weapon[2]->currentAmmo > 0)) { game->selectedWeapon = game->weapon[2]; game->weaponIndex = 2; player->reload = 100; return; } game->selectedWeapon = game->weapon[0]; game->weaponIndex = 0; player->reload = 100; } void playerIssueOrders() { if (entityManager->bossList.getSize() > 0) { return; } if (engine->keyState[SDLK_7]) { game->setInfoMessage(INFO_HINT, _("Orders : Follow")); game->currentOrders = ORDERS_FOLLOW; engine->keyState[SDLK_7] = 0; resetBlobOrders(); } if (engine->keyState[SDLK_8]) { game->setInfoMessage(INFO_HINT, _("Orders : Wait")); game->currentOrders = ORDERS_WAIT; engine->keyState[SDLK_8] = 0; resetBlobOrders(); } if (engine->keyState[SDLK_9]) { game->setInfoMessage(INFO_HINT, _("Orders : Use")); game->currentOrders = ORDERS_USE; engine->keyState[SDLK_9] = 0; resetBlobOrders(); resetBlobOrders(); } if (engine->keyState[SDLK_0]) { game->setInfoMessage(INFO_HINT, _("Orders : Retreat")); game->currentOrders = ORDERS_RETREAT; engine->keyState[SDLK_0] = 0; resetBlobOrders(); } } void playerFireWeapon(Weapon *weapon) { Vector v; String s; if ((weapon->currentAmmo > 0) || (game->cheatAmmo)) { // can't fire underwater :) if ((player->liquidLevel > 1) && ((WeaponDef*)weapon->definition->type != WEP_PISTOL)) { return; } if (player->target != NULL) { faceTarget(player); v = player->target->position; switch (weapon->definition->type) { case WEP_GRENADE: case WEP_GRENADE_LAUNCHER: case WEP_CLUSTER_LAUNCHER: v = getLeadingTargetPosition(player, player->target); break; } fireWeapon(player, (WeaponDef*)weapon->definition, player->position, v); } else { v = player->position; calculatePlayerAimDirection(); fireWeapon(player, (WeaponDef*)weapon->definition, v, game->targetLock); } WeaponDef *weaponDef = (WeaponDef*)weapon->definition; if (!weaponDef->unlimited) { if (!game->cheatAmmo) { weapon->currentAmmo--; } textureManager->removeTexture("str_current_ammo"); s.setText("str_weapon_%d", game->weaponIndex); textureManager->removeTexture(s.getText()); if (weapon->currentAmmo == 0) { game->mustChangeWeapon = true; if (weaponDef->type == WEP_DESIMILATOR) { weapon->currentAmmo = -100; } } } if (weaponDef->type == WEP_SHOTGUN) { if ((weapon->currentAmmo % 2) != 0) { player->reload *= 0.25; } } } else { audio->playSound(SND_WEAPON_EMPTY, CH_PLAYER, 128); player->reload = 8; } } void doPlayerCycleWeapon(int dir) { Math::wrap(&(game->weaponIndex += dir), 0, 3); while (game->weapon[game->weaponIndex] == NULL) { Math::wrap(&(game->weaponIndex += dir), 0, 3); } game->selectedWeapon = game->weapon[game->weaponIndex]; game->resetControl(CTRL_CYCLE_WEAPON); textureManager->removeTexture("str_current_ammo"); } void doPlayerWeapons() { if ((player->target == NULL) && (game->autoLockTarget)) { playerTargetEnemy(); } if (game->isControl(CTRL_LOCK_TARGET)) { player->target = NULL; game->autoLockTarget = !game->autoLockTarget; game->cameraAimHeight = 100; game->resetControl(CTRL_LOCK_TARGET); } if (game->isControl(CTRL_CYCLE_TARGET)) { if (game->autoLockTarget) { if (player->target != NULL) { playerCycleTargets(); } else { playerTargetEnemy(); } } game->resetControl(CTRL_CYCLE_TARGET); } if (game->isControl(CTRL_CYCLE_WEAPON)) { doPlayerCycleWeapon(1); } if (engine->keyState[SDLK_1]) { game->weaponIndex = 0; game->selectedWeapon = game->weapon[game->weaponIndex]; engine->keyState[SDLK_1] = 0; } if (engine->keyState[SDLK_2]) { if (game->weapon[1] != NULL) { game->weaponIndex = 1; game->selectedWeapon = game->weapon[1]; } engine->keyState[SDLK_2] = 0; } if (engine->keyState[SDLK_3]) { if (game->weapon[2] != NULL) { game->weaponIndex = 2; game->selectedWeapon = game->weapon[2]; } engine->keyState[SDLK_3] = 0; } if (engine->keyState[SDLK_4]) { game->weaponIndex = 3; if (game->weapon[game->weaponIndex]->currentAmmo > 0) { game->selectedWeapon = game->weapon[3]; } engine->keyState[SDLK_4] = 0; } if (mouse->buttonState[SDL_BUTTON_WHEELUP]) { doPlayerCycleWeapon(-1); mouse->buttonState[SDL_BUTTON_WHEELUP] = 0; } if (mouse->buttonState[SDL_BUTTON_WHEELDOWN]) { doPlayerCycleWeapon(1); mouse->buttonState[SDL_BUTTON_WHEELDOWN] = 0; } if (game->isControl(CTRL_FIRE)) { if (player->target != NULL) { game->autoFaceTimer = 100; } if (player->reload == 0) { playerFireWeapon(game->selectedWeapon); } } else if (game->isControl(CTRL_QUICK_GRENADE)) { if (game->weapon[3] != NULL) { if (player->target != NULL) { game->autoFaceTimer = 100; } if (player->reload == 0) { playerFireWeapon(game->weapon[3]); textureManager->removeTexture("str_weapon_3"); } } } if ((game->mustChangeWeapon) && (!game->isControl(CTRL_FIRE))) { playerSelectBestWeapon(); } if (game->autoFaceTimer > 0) { Math::limit(&(--game->autoFaceTimer), 0, 200); faceTarget(player); } } void movePlayerCamera(float turnSpeed) { if (((!game->autoLockTarget) && (game->cameraAimHeight == 100)) || (game->autoLockTarget)) { game->cameraHeight += (turnSpeed * engine->getTimeDifference(TD_LOGIC)); Math::limit(&game->cameraHeight, 10, 115); } if (((!game->autoLockTarget) && (game->cameraHeight == 10)) || (game->autoLockTarget)) { float dir = (turnSpeed < 0) ? 5 : -5; game->cameraAimHeight += (dir * engine->getTimeDifference(TD_LOGIC)); Math::limit(&game->cameraAimHeight, 100, 500); } } void movePlayerAnalog() { if (engine->joystick == NULL) { return; } float speed; if (engine->axis1X <= -game->joystickDeadZone) { speed = engine->axis1X; speed /= ANALOG_MOVE; player->velocity.x += (PLAYER_ACCEL * speed * cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * speed * sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = 270 + toDegrees(game->cameraAdjustedRadian); } if (engine->axis1X >= game->joystickDeadZone) { speed = engine->axis1X; speed /= ANALOG_MOVE; player->velocity.x += (PLAYER_ACCEL * speed * cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * speed * sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = 90 + toDegrees(game->cameraAdjustedRadian); } if (engine->axis1Y >= game->joystickDeadZone) { speed = ~engine->axis1Y; speed /= ANALOG_MOVE; player->velocity.x += (PLAYER_ACCEL * speed * -sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * speed * cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = toDegrees(game->cameraAdjustedRadian); if (engine->axis1X <= -game->joystickDeadZone) { player->rotation.x -= 45; } if (engine->axis1X >= game->joystickDeadZone) { player->rotation.x += 45; } } if (engine->axis1Y <= -game->joystickDeadZone) { speed = ~engine->axis1Y; speed /= ANALOG_MOVE; player->velocity.x += (PLAYER_ACCEL * speed * -sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * speed * cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = 180 + toDegrees(game->cameraAdjustedRadian); if (engine->axis1X <= -game->joystickDeadZone) { player->rotation.x += 45; } if (engine->axis1X >= game->joystickDeadZone) { player->rotation.x -= 45; } } } void movePlayer() { float moveSpeed = 1.2; bool walkTurn = false; if (player->target != NULL) { player->target->referenced = true; if (Math::getDistance(player->position.x, player->position.y, player->target->position.x, player->target->position.y) > PLAYER_TARGET_RANGE) { player->target = NULL; } else if (player->target->health <= 0) { player->target = NULL; } else if ((player->target->flags & EF_TELEPORTING) || (player->target->flags & EF_VANISHED)) { player->target = NULL; } else if (!bsp->canSightTarget(player->position, player->target->position)) { player->target = NULL; } } if (game->isControl(CTRL_LEFT)) { player->velocity.x += (PLAYER_ACCEL * moveSpeed * -cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * moveSpeed * -sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = 270 + toDegrees(game->cameraAdjustedRadian); walkTurn = true; } if (game->isControl(CTRL_RIGHT)) { player->velocity.x += (PLAYER_ACCEL * moveSpeed * cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * moveSpeed * sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = 90 + toDegrees(game->cameraAdjustedRadian); walkTurn = true; } if (game->isControl(CTRL_UP)) { player->velocity.x += (PLAYER_ACCEL * moveSpeed * -sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC);; player->velocity.y += (PLAYER_ACCEL * moveSpeed * cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC);; player->rotation.x = 180 + toDegrees(game->cameraAdjustedRadian); if (game->isControl(CTRL_LEFT)) { player->rotation.x += 45; } if (game->isControl(CTRL_RIGHT)) { player->rotation.x -= 45; } walkTurn = true; } if (game->isControl(CTRL_DOWN)) { player->velocity.x += (PLAYER_ACCEL * moveSpeed * sin(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->velocity.y += (PLAYER_ACCEL * moveSpeed * -cos(game->cameraAdjustedRadian)) * engine->getTimeDifference(TD_LOGIC); player->rotation.x = toDegrees(game->cameraAdjustedRadian); if (game->isControl(CTRL_LEFT)) { player->rotation.x -= 45; } if (game->isControl(CTRL_RIGHT)) { player->rotation.x += 45; } walkTurn = true; } if (game->isControl(CTRL_JUMP)) { if ((player->liquidLevel == 0) && (player->flags & EF_ONGROUND)) { player->velocity.z = 4.0 / 3.0; engine->keyState[SDLK_SPACE] = 0; player->flags &= ~EF_ONGROUND; } else if (player->liquidLevel > 0) { player->velocity.z += (0.01 * engine->getTimeDifference(TD_LOGIC)); } } else if (player->liquidLevel > 0) { if (player->velocity.z > -0.5) { player->velocity.z -= (0.0025 * engine->getTimeDifference(TD_LOGIC)); } } if (game->isControl(CTRL_STATUS)) { game->resetControl(CTRL_STATUS); showMissionStatusPanel(); } #if DEV if (engine->keyState[SDLK_f]) { player->velocity.z = 0; player->position.z += (engine->getTimeDifference(TD_LOGIC)); } if (engine->keyState[SDLK_v]) { player->velocity.z = 0; player->position.z -= (engine->getTimeDifference(TD_LOGIC)); } #endif } /* * Keeps the player a certain distance away from the edges by tracing down * in a large box around them. If it's possible to hit the target then the player * isn't in a safe place */ void assignCheckPoint() { Vector position; if (!(player->flags & EF_ONGROUND)) { return; } if (!(player->contentType & CONTENTS_SOLID)) { return; } position = player->position; position.x -= 30; position.y -= 30; position.z -= 4; if (bsp->canSightTarget(player->position, position)) { return; } position = player->position; position.x += 30; position.y -= 30; position.z -= 4; if (bsp->canSightTarget(player->position, position)) { return; } position = player->position; position.x += 30; position.y += 30; position.z -= 4; if (bsp->canSightTarget(player->position, position)) { return; } position = player->position; position.x -= 30; position.y += 30; position.z -= 4; if (bsp->canSightTarget(player->position, position)) { return; } game->checkpoint = player->position; } void handlePlayerCamera() { // ======= Mouse ====== int x, y; float turnSpeed; SDL_GetRelativeMouseState(&x, &y); turnSpeed = -x; turnSpeed /= game->mouseSensitivity; game->changeCameraRotation(turnSpeed * engine->getTimeDifference(TD_LOGIC)); turnSpeed = y; turnSpeed /= (game->mouseSensitivity / 50); if (turnSpeed != 0) { movePlayerCamera(turnSpeed); } // === Joypad ====== if ((engine->axis2X <= -game->joystickDeadZone) || (engine->axis2X >= game->joystickDeadZone)) { turnSpeed = -engine->axis2X / 2000000.0; game->changeCameraRotation(turnSpeed * engine->getTimeDifference(TD_LOGIC)); } if ((engine->axis2Y <= -game->joystickDeadZone) || (engine->axis2Y >= game->joystickDeadZone)) { turnSpeed = engine->axis2Y / 35000.0; movePlayerCamera(turnSpeed); } // === Keyboard ====== if (game->isControl(CTRL_ROTATE_LEFT)) { game->changeCameraRotation(2 * PI * engine->getTimeDifference(TD_LOGIC) / 120.0); } if (game->isControl(CTRL_ROTATE_RIGHT)) { game->changeCameraRotation(-2 * PI * engine->getTimeDifference(TD_LOGIC) / 120.0); } } void doPlayer() { if (game->missionStatus == MISSION_INPROGRESS) { if (player->contentType & CONTENTS_SLIME) { player->applyDamage(4, DAMAGE_SPECIAL); player->shield = -2.5; player->helpless = RAND_MAX; game->setMissionStatus((player->health > 0) ? MISSION_PLAYER_OUT : MISSION_FAILED); game->alterStat(_("Times Fallen In Slime"), 1); } else if (player->contentType & CONTENTS_LAVA) { player->applyDamage(8, DAMAGE_SPECIAL); player->shield = -2.5; player->helpless = RAND_MAX; game->setMissionStatus((player->health > 0) ? MISSION_PLAYER_OUT : MISSION_FAILED); game->alterStat(_("Times Fallen In Lava"), 1); } else if (player->contentType & CONTENTS_PLAYERCLIP) { player->applyDamage(8, DAMAGE_SPECIAL); player->shield = -2.5; player->helpless = RAND_MAX; game->setMissionStatus((player->health > 0) ? MISSION_PLAYER_OUT : MISSION_FAILED); } } if (game->cheatHealth) { player->health = player->maxHealth; } if (game->cheatShield) { player->shield = player->maxShield; } if (game->cheatOxygen) { player->oxygen = MAX_OXYGEN_IND; } if (!(player->flags & (EF_DYING|EF_DEAD))) { if (player->oxygen == 0) { if ((((int)graphics->getAnimTimer()) % 45) <= 0) { audio->playSound(SND_DROWNING, CH_PLAYER, 128); } } } handlePlayerCamera(); if ((player->health <= 0) || (player->action != NULL) || (player->helpless)) { return; } assignCheckPoint(); for (int i = 0 ; i < engine->getInertiaRate() ; i++) { player->velocity.x *= 0.95; player->velocity.y *= 0.95; } if ((player->velocity.x > -EPSILON) && (player->velocity.x < EPSILON)) { player->velocity.x = 0; } if ((player->velocity.y > -EPSILON) && (player->velocity.y < EPSILON)) { player->velocity.y = 0; } player->helpless = 0; player->rotation.z = 0; movePlayer(); movePlayerAnalog(); if (!game->autoLockTarget) { player->rotation.x = 180 + toDegrees(game->cameraAdjustedRadian); calculatePlayerAimDirection(); } doPlayerWeapons(); playerIssueOrders(); Math::limit(&player->velocity.x, -1.2, 1.2); Math::limit(&player->velocity.y, -1.2, 1.2); }