You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
Merge upstream/main into main
This commit is contained in:
@@ -0,0 +1 @@
|
||||
ALTER TABLE `rooms` ADD COLUMN `allow_underpass` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `move_diagonally`;
|
||||
@@ -0,0 +1,71 @@
|
||||
-- ============================================================
|
||||
-- Camera - Database Setup
|
||||
-- Run this SQL manually before using the camera feature.
|
||||
-- ============================================================
|
||||
|
||||
-- -----------------------------------------
|
||||
-- Table: camera_web (stores published photos)
|
||||
-- -----------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `camera_web` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` INT(11) NOT NULL,
|
||||
`room_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`timestamp` INT(11) NOT NULL DEFAULT 0,
|
||||
`url` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_camera_web_user_id` (`user_id`),
|
||||
INDEX `idx_camera_web_timestamp` (`timestamp`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------
|
||||
-- Emulator Settings for Camera
|
||||
-- -----------------------------------------
|
||||
-- Uses INSERT IGNORE so existing values are not overwritten.
|
||||
|
||||
-- Base URL where camera photos are served (include trailing slash)
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('camera.url', 'http://localhost/camera/');
|
||||
|
||||
-- Filesystem path where full-size camera photos are saved (include trailing slash)
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('imager.location.output.camera', '/path/to/www/camera/');
|
||||
|
||||
-- Filesystem path where room thumbnail images are saved (include trailing slash)
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('imager.location.output.thumbnail', '/path/to/www/thumbnails/');
|
||||
|
||||
-- Item ID for the wall photo item (must exist in items_base with interaction type "external_image")
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('camera.item_id', '0');
|
||||
|
||||
-- Price in credits to purchase a photo as a wall item
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('camera.price.credits', '2');
|
||||
|
||||
-- Price in seasonal points to purchase a photo as a wall item
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('camera.price.points', '0');
|
||||
|
||||
-- Price in seasonal points to publish a photo to the web
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('camera.price.points.publish', '1');
|
||||
|
||||
-- JSON template for photo item extradata
|
||||
-- Available placeholders: %timestamp%, %room_id%, %url%, %id%
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('camera.extradata', '{"t":"%timestamp%","u":"%id%","m":"","s":"%room_id%","w":"%url%"}');
|
||||
|
||||
-- -----------------------------------------
|
||||
-- Emulator Texts for Camera
|
||||
-- -----------------------------------------
|
||||
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
|
||||
('camera.permission', 'You do not have permission to use the camera.');
|
||||
|
||||
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
|
||||
('camera.wait', 'Please wait %seconds% more seconds before taking another photo.');
|
||||
|
||||
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
|
||||
('camera.error.creation', 'An error occurred while processing your photo. Please try again.');
|
||||
|
||||
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
|
||||
('camera.daily.limit', 'You have reached the daily photo limit. Try again tomorrow.');
|
||||
@@ -7,7 +7,6 @@ import com.eu.habbo.core.*;
|
||||
import com.eu.habbo.core.consolecommands.ConsoleCommand;
|
||||
import com.eu.habbo.database.Database;
|
||||
import com.eu.habbo.habbohotel.GameEnvironment;
|
||||
import com.eu.habbo.networking.camera.CameraClient;
|
||||
import com.eu.habbo.networking.gameserver.GameServer;
|
||||
import com.eu.habbo.networking.rconserver.RCONServer;
|
||||
import com.eu.habbo.plugin.PluginManager;
|
||||
@@ -64,7 +63,6 @@ public final class Emulator {
|
||||
private static TextsManager texts;
|
||||
private static GameServer gameServer;
|
||||
private static RCONServer rconServer;
|
||||
private static CameraClient cameraClient;
|
||||
private static Logging logging;
|
||||
private static Database database;
|
||||
private static DatabaseLogger databaseLogger;
|
||||
@@ -132,6 +130,26 @@ public final class Emulator {
|
||||
Emulator.pluginManager.reload();
|
||||
Emulator.getPluginManager().fireEvent(new EmulatorConfigUpdatedEvent());
|
||||
Emulator.texts = new TextsManager();
|
||||
|
||||
Emulator.config.register("camera.url", "http://localhost/camera/");
|
||||
Emulator.config.register("imager.location.output.camera", "/public/camera/");
|
||||
Emulator.config.register("imager.location.output.thumbnail", "/public/camera/thumbnails/");
|
||||
Emulator.config.register("camera.price.points.publish", "1");
|
||||
Emulator.config.register("camera.price.points.publish.type", "5");
|
||||
Emulator.config.register("camera.publish.delay", "180");
|
||||
Emulator.config.register("camera.price.credits", "2");
|
||||
Emulator.config.register("camera.price.points", "0");
|
||||
Emulator.config.register("camera.price.points.type", "5");
|
||||
Emulator.config.register("camera.render.delay", "5");
|
||||
Emulator.texts.register("camera.permission", "You don't have permission to use the camera!");
|
||||
Emulator.texts.register("camera.wait", "Please wait %seconds% seconds before making another picture.");
|
||||
Emulator.texts.register("camera.error.creation", "Failed to create your picture. *sadpanda*");
|
||||
|
||||
File thumbnailDir = new File(Emulator.config.getValue("imager.location.output.thumbnail"));
|
||||
if (!thumbnailDir.exists()) {
|
||||
thumbnailDir.mkdirs();
|
||||
}
|
||||
|
||||
new CleanerThread();
|
||||
Emulator.gameServer = new GameServer(getConfig().getValue("game.host", "127.0.0.1"), getConfig().getInt("game.port", 30000));
|
||||
Emulator.rconServer = new RCONServer(getConfig().getValue("rcon.host", "127.0.0.1"), getConfig().getInt("rcon.port", 30001));
|
||||
@@ -231,7 +249,6 @@ public final class Emulator {
|
||||
|
||||
if (Emulator.pluginManager != null)
|
||||
tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStartShutdownEvent()));
|
||||
if (Emulator.cameraClient != null) tryShutdown(() -> Emulator.cameraClient.disconnect());
|
||||
if (Emulator.rconServer != null) tryShutdown(() -> Emulator.rconServer.stop());
|
||||
if (Emulator.gameEnvironment != null) tryShutdown(() -> Emulator.gameEnvironment.dispose());
|
||||
if (Emulator.pluginManager != null)
|
||||
@@ -318,14 +335,6 @@ public final class Emulator {
|
||||
return badgeImager;
|
||||
}
|
||||
|
||||
public static CameraClient getCameraClient() {
|
||||
return cameraClient;
|
||||
}
|
||||
|
||||
public static synchronized void setCameraClient(CameraClient client) {
|
||||
cameraClient = client;
|
||||
}
|
||||
|
||||
public static int getTimeStarted() {
|
||||
return timeStarted;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ public abstract class ConsoleCommand {
|
||||
addCommand(new ConsoleShutdownCommand());
|
||||
addCommand(new ConsoleInfoCommand());
|
||||
addCommand(new ConsoleTestCommand());
|
||||
addCommand(new ConsoleReconnectCameraCommand());
|
||||
addCommand(new ShowInteractionsCommand());
|
||||
addCommand(new ShowRCONCommands());
|
||||
addCommand(new ThankyouArcturusCommand());
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.eu.habbo.core.consolecommands;
|
||||
|
||||
import com.eu.habbo.networking.camera.CameraClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConsoleReconnectCameraCommand extends ConsoleCommand {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleReconnectCameraCommand.class);
|
||||
|
||||
public ConsoleReconnectCameraCommand() {
|
||||
super("camera", "Attempt to reconnect to the camera server.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String[] args) throws Exception {
|
||||
LOGGER.info("Connecting to the camera...");
|
||||
CameraClient.attemptReconnect = true;
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,6 @@ public class CommandHandler {
|
||||
addCommand(new ChangeNameCommand());
|
||||
addCommand(new ChatTypeCommand());
|
||||
addCommand(new CommandsCommand());
|
||||
addCommand(new ConnectCameraCommand());
|
||||
addCommand(new ControlCommand());
|
||||
addCommand(new CoordsCommand());
|
||||
addCommand(new CreditsCommand());
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.eu.habbo.habbohotel.commands;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
|
||||
public class ConnectCameraCommand extends Command {
|
||||
public ConnectCameraCommand() {
|
||||
super("cmd_connect_camera", Emulator.getTexts().getValue("commands.keys.cmd_connect_camera").split(";"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GameClient gameClient, String[] params) throws Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+61
-7
@@ -1,23 +1,24 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.games.football;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionDefault;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomLayout;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer;
|
||||
import com.eu.habbo.threading.runnables.RebugKickBallAction;
|
||||
import com.eu.habbo.util.pathfinding.Direction8;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Rebug-style football interaction.
|
||||
* Uses simplified momentum-decay physics with 180-degree bounce.
|
||||
* Set interaction_type to "rebug_football" on a ball item to use this instead of the default football physics.
|
||||
*/
|
||||
public class InteractionRebugFootball extends InteractionDefault {
|
||||
|
||||
private RebugKickBallAction currentThread;
|
||||
private Direction8 lastDribbleDirection;
|
||||
|
||||
public InteractionRebugFootball(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
@@ -43,12 +44,65 @@ public class InteractionRebugFootball extends InteractionDefault {
|
||||
public void onWalkOn(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
|
||||
super.onWalkOn(roomUnit, room, objects);
|
||||
|
||||
Direction8 userDir = Direction8.getDirection(roomUnit.getBodyRotation().getValue());
|
||||
this.lastDribbleDirection = userDir;
|
||||
|
||||
RoomTile goal = roomUnit.getGoal();
|
||||
if (goal != null && goal.x == this.getX() && goal.y == this.getY()) {
|
||||
this.kick(room, roomUnit, 55);
|
||||
} else {
|
||||
this.kick(room, roomUnit, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalkOff(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
|
||||
super.onWalkOff(roomUnit, room, objects);
|
||||
|
||||
if (objects != null && objects.length >= 2 && objects[1] instanceof RoomTile && objects[0] instanceof RoomTile) {
|
||||
RoomTile fromTile = (RoomTile) objects[0];
|
||||
RoomTile nextTile = (RoomTile) objects[1];
|
||||
|
||||
int dx = nextTile.x - fromTile.x;
|
||||
int dy = nextTile.y - fromTile.y;
|
||||
Direction8 walkDir = Direction8.fromDelta(dx, dy);
|
||||
|
||||
if (this.lastDribbleDirection != null && walkDir.getRot() == this.lastDribbleDirection.getRot()) {
|
||||
this.kick(room, roomUnit, 55);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentThread != null) {
|
||||
this.currentThread.dead = true;
|
||||
this.currentThread = null;
|
||||
}
|
||||
this.setExtradata("0");
|
||||
room.sendComposer(new ItemStateComposer(this).compose());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
|
||||
super.onClick(client, room, objects);
|
||||
|
||||
if (client == null) return;
|
||||
RoomUnit unit = client.getHabbo().getRoomUnit();
|
||||
if (RoomLayout.tilesAdjecent(unit.getCurrentLocation(), room.getLayout().getTile(this.getX(), this.getY()))) {
|
||||
this.kick(room, unit, 55);
|
||||
}
|
||||
}
|
||||
|
||||
private void kick(Room room, RoomUnit kicker, int momentum) {
|
||||
boolean wasMoving = this.currentThread != null && !this.currentThread.dead && !this.currentThread.isDribble();
|
||||
|
||||
if (this.currentThread != null) {
|
||||
this.currentThread.dead = true;
|
||||
}
|
||||
|
||||
boolean hasPath = !roomUnit.getPath().isEmpty();
|
||||
this.currentThread = new RebugKickBallAction(this, room, roomUnit, hasPath);
|
||||
Direction8 direction = Direction8.getDirection(kicker.getBodyRotation().getValue());
|
||||
boolean zigzag = wasMoving && momentum > 0;
|
||||
|
||||
this.currentThread = new RebugKickBallAction(this, room, direction, momentum, zigzag);
|
||||
Emulator.getThreading().run(this.currentThread, 50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
private volatile boolean promoted;
|
||||
private volatile int tradeMode;
|
||||
private volatile boolean moveDiagonally;
|
||||
private volatile boolean allowUnderpass;
|
||||
private volatile boolean jukeboxActive;
|
||||
private volatile boolean hideWired;
|
||||
private RoomPromotion promotion;
|
||||
@@ -241,6 +242,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
|
||||
this.tradeMode = set.getInt("trade_mode");
|
||||
this.moveDiagonally = set.getString("move_diagonally").equals("1");
|
||||
this.allowUnderpass = set.getString("allow_underpass").equals("1");
|
||||
|
||||
this.preLoaded = true;
|
||||
this.allowBotsWalk = true;
|
||||
@@ -1079,7 +1081,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
if (this.needsUpdate) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource()
|
||||
.getConnection(); PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ? WHERE id = ?")) {
|
||||
"UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ?, allow_underpass = ? WHERE id = ?")) {
|
||||
statement.setString(1, this.name);
|
||||
statement.setString(2, this.description);
|
||||
statement.setString(3, this.password);
|
||||
@@ -1128,7 +1130,8 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
statement.setString(37, this.ownerName);
|
||||
statement.setString(38, this.jukeboxActive ? "1" : "0");
|
||||
statement.setString(39, this.hideWired ? "1" : "0");
|
||||
statement.setInt(40, this.id);
|
||||
statement.setString(40, this.allowUnderpass ? "1" : "0");
|
||||
statement.setInt(41, this.id);
|
||||
statement.executeUpdate();
|
||||
this.needsUpdate = false;
|
||||
} catch (SQLException e) {
|
||||
@@ -1410,6 +1413,14 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
this.allowWalkthrough = allowWalkthrough;
|
||||
}
|
||||
|
||||
public boolean isAllowUnderpass() {
|
||||
return this.allowUnderpass;
|
||||
}
|
||||
|
||||
public void setAllowUnderpass(boolean allowUnderpass) {
|
||||
this.allowUnderpass = allowUnderpass;
|
||||
}
|
||||
|
||||
public boolean isAllowBotsWalk() {
|
||||
return this.allowBotsWalk;
|
||||
}
|
||||
|
||||
@@ -448,6 +448,48 @@ public class RoomItemManager {
|
||||
return highestItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the top walkable item at a position, considering underpass.
|
||||
* If the topmost item is elevated enough to walk under, returns the highest item at walk surface level instead.
|
||||
*/
|
||||
public HabboItem getWalkableItemAt(int x, int y) {
|
||||
HabboItem topItem = this.getTopItemAt(x, y);
|
||||
if (topItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If underpass is disabled for this room, just return the top item
|
||||
if (!this.room.isAllowUnderpass()) {
|
||||
return topItem;
|
||||
}
|
||||
|
||||
// If the top item is walkable, just return it
|
||||
if (topItem.isWalkable() || topItem.getBaseItem().allowWalk() || topItem.getBaseItem().allowSit() || topItem.getBaseItem().allowLay()) {
|
||||
return topItem;
|
||||
}
|
||||
|
||||
// Check for underpass: get the walk surface height
|
||||
double walkSurface = this.room.getLayout() != null ? this.room.getLayout().getHeightAtSquare(x, y) : 0;
|
||||
HabboItem walkSurfaceItem = null;
|
||||
|
||||
for (HabboItem item : this.getItemsAt(x, y)) {
|
||||
if (item.isWalkable() || item.getBaseItem().allowWalk() || item.getBaseItem().allowSit() || item.getBaseItem().allowLay()) {
|
||||
double itemTop = item.getZ() + Item.getCurrentHeight(item);
|
||||
if (itemTop > walkSurface) {
|
||||
walkSurface = itemTop;
|
||||
walkSurfaceItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's enough clearance under the top blocking item, return the walk surface item
|
||||
if (topItem.getZ() - walkSurface >= RoomLayout.UNDERPASS_HEIGHT) {
|
||||
return walkSurfaceItem;
|
||||
}
|
||||
|
||||
return topItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the top item from a set of tiles.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ public class RoomLayout {
|
||||
protected static final int DIAGONALMOVEMENTCOST = 14;
|
||||
public static double MAXIMUM_STEP_HEIGHT = 1.5;
|
||||
public static boolean ALLOW_FALLING = true;
|
||||
public static double UNDERPASS_HEIGHT = 1.5;
|
||||
public boolean CANMOVEDIAGONALY = true;
|
||||
private String name;
|
||||
private short doorX;
|
||||
|
||||
@@ -91,9 +91,41 @@ public class RoomTileManager {
|
||||
tallestItem = item;
|
||||
}
|
||||
|
||||
if (this.room.isAllowUnderpass() && result == RoomTileState.BLOCKED && tallestItem != null) {
|
||||
double walkSurface = this.getUnderpassWalkHeight(tile, items, exclude);
|
||||
if (tallestItem.getZ() - walkSurface >= RoomLayout.UNDERPASS_HEIGHT) {
|
||||
result = RoomTileState.OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the walk surface height for underpass checks.
|
||||
* Returns the floor height or the top of the highest walkable item below any blocking items.
|
||||
*/
|
||||
private double getUnderpassWalkHeight(RoomTile tile, THashSet<HabboItem> items, HabboItem exclude) {
|
||||
RoomLayout layout = this.room.getLayout();
|
||||
double walkHeight = layout != null ? layout.getHeightAtSquare(tile.x, tile.y) : 0;
|
||||
|
||||
if (items != null) {
|
||||
for (HabboItem item : items) {
|
||||
if (exclude != null && item == exclude) {
|
||||
continue;
|
||||
}
|
||||
if (item.isWalkable() || item.getBaseItem().allowWalk() || item.getBaseItem().allowSit() || item.getBaseItem().allowLay()) {
|
||||
double itemTop = item.getZ() + Item.getCurrentHeight(item);
|
||||
if (itemTop > walkHeight) {
|
||||
walkHeight = itemTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return walkHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the tile state based on a specific item.
|
||||
*/
|
||||
@@ -193,7 +225,22 @@ public class RoomTileManager {
|
||||
HabboItem item = this.room.getItemManager().getTopItemAt(x, y, exclude);
|
||||
if (item != null) {
|
||||
canStack = item.getBaseItem().allowStack();
|
||||
height = item.getZ() + (item.getBaseItem().allowSit() ? 0 : Item.getCurrentHeight(item));
|
||||
double itemTop = item.getZ() + (item.getBaseItem().allowSit() ? 0 : Item.getCurrentHeight(item));
|
||||
|
||||
// Underpass: if the top item is blocking but high enough to walk under, use floor height
|
||||
if (this.room.isAllowUnderpass() && !item.isWalkable() && !item.getBaseItem().allowWalk() && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay()) {
|
||||
RoomLayout layout2 = this.room.getLayout();
|
||||
RoomTile tile = layout2 != null ? layout2.getTile(x, y) : null;
|
||||
THashSet<HabboItem> allItems = tile != null ? this.room.getItemManager().getItemsAt(tile) : null;
|
||||
double walkSurface = this.getUnderpassWalkHeight(tile, allItems, exclude);
|
||||
if (item.getZ() - walkSurface >= RoomLayout.UNDERPASS_HEIGHT) {
|
||||
height = walkSurface;
|
||||
} else {
|
||||
height = itemTop;
|
||||
}
|
||||
} else {
|
||||
height = itemTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (calculateHeightmap) {
|
||||
@@ -396,6 +443,14 @@ public class RoomTileManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Underpass: if top item blocks but is high enough, allow walking under
|
||||
if (this.room.isAllowUnderpass() && !canWalk && topItem != null) {
|
||||
double walkSurface = this.getUnderpassWalkHeight(roomTile, items, null);
|
||||
if (topItem.getZ() - walkSurface >= RoomLayout.UNDERPASS_HEIGHT) {
|
||||
canWalk = true;
|
||||
}
|
||||
}
|
||||
|
||||
return canWalk;
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ public class RoomUnit {
|
||||
}
|
||||
}
|
||||
|
||||
HabboItem item = room.getTopItemAt(next.x, next.y);
|
||||
HabboItem item = room.getItemManager().getWalkableItemAt(next.x, next.y);
|
||||
boolean canSitNextTile = room.canSitAt(next.x, next.y);
|
||||
boolean canLayNextTile = room.canLayAt(next.x, next.y);
|
||||
|
||||
|
||||
+30
-24
@@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.camera;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.camera.CameraPublishWaitMessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.catalog.NotEnoughPointsTypeComposer;
|
||||
@@ -16,49 +17,54 @@ import java.sql.SQLException;
|
||||
public class CameraPublishToWebEvent extends MessageHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraPublishToWebEvent.class);
|
||||
|
||||
public static int CAMERA_PUBLISH_POINTS = 5;
|
||||
public static int CAMERA_PUBLISH_POINTS_TYPE = 0;
|
||||
public static int CAMERA_PUBLISH_POINTS = 1;
|
||||
public static int CAMERA_PUBLISH_POINTS_TYPE = 5;
|
||||
public static int CAMERA_PUBLISH_DELAY = 180;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
public void handle() {
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
|
||||
if (habbo == null) return;
|
||||
if (habbo.getHabboInfo().getPhotoTimestamp() == 0) return;
|
||||
if (habbo.getHabboInfo().getPhotoJSON().isEmpty()) return;
|
||||
if (!habbo.getHabboInfo().getPhotoJSON().contains(habbo.getHabboInfo().getPhotoTimestamp() + "")) return;
|
||||
|
||||
if (habbo.getHabboInfo().getCurrencyAmount(CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS_TYPE) < CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS) {
|
||||
this.client.sendResponse(new NotEnoughPointsTypeComposer(false, true, CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS));
|
||||
HabboInfo habboInfo = habbo.getHabboInfo();
|
||||
|
||||
int points = habboInfo.getCurrencyAmount(CAMERA_PUBLISH_POINTS_TYPE);
|
||||
if (points < CAMERA_PUBLISH_POINTS) {
|
||||
String currencyName = Emulator.getTexts().getValue("seasonal.name." + CAMERA_PUBLISH_POINTS_TYPE, "currency");
|
||||
habbo.alert("You don't have enough " + currencyName + "!");
|
||||
this.client.sendResponse(new NotEnoughPointsTypeComposer(false, true, CAMERA_PUBLISH_POINTS_TYPE));
|
||||
return;
|
||||
}
|
||||
|
||||
int timestamp = Emulator.getIntUnixTimestamp();
|
||||
int photoTimestamp = habboInfo.getPhotoTimestamp();
|
||||
String photoJSON = habboInfo.getPhotoJSON();
|
||||
if (photoTimestamp == 0 || photoJSON.isEmpty() || !photoJSON.contains(Integer.toString(photoTimestamp)))
|
||||
return;
|
||||
|
||||
boolean isOk = false;
|
||||
int cooldownLeft = Math.max(0, Emulator.getConfig().getInt("camera.publish.delay") - (timestamp - this.client.getHabbo().getHabboInfo().getWebPublishTimestamp()));
|
||||
int currentTimestamp = Emulator.getIntUnixTimestamp();
|
||||
int timeSinceLastPublish = currentTimestamp - habboInfo.getWebPublishTimestamp();
|
||||
|
||||
if (cooldownLeft == 0) {
|
||||
UserPublishPictureEvent publishPictureEvent = new UserPublishPictureEvent(this.client.getHabbo(), this.client.getHabbo().getHabboInfo().getPhotoURL(), timestamp, this.client.getHabbo().getHabboInfo().getPhotoRoomId());
|
||||
if (timeSinceLastPublish < CAMERA_PUBLISH_DELAY) {
|
||||
int wait = CAMERA_PUBLISH_DELAY - timeSinceLastPublish;
|
||||
this.client.sendResponse(new CameraPublishWaitMessageComposer(false, wait, habboInfo.getPhotoURL()));
|
||||
} else {
|
||||
UserPublishPictureEvent publishPictureEvent = new UserPublishPictureEvent(habbo, habboInfo.getPhotoURL(), currentTimestamp, habboInfo.getPhotoRoomId());
|
||||
|
||||
if (!Emulator.getPluginManager().fireEvent(publishPictureEvent).isCancelled()) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO camera_web (user_id, room_id, timestamp, url) VALUES (?, ?, ?, ?)")) {
|
||||
statement.setInt(1, this.client.getHabbo().getHabboInfo().getId());
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO camera_web (user_id, room_id, timestamp, url) VALUES (?, ?, ?, ?)")) {
|
||||
statement.setInt(1, habboInfo.getId());
|
||||
statement.setInt(2, publishPictureEvent.roomId);
|
||||
statement.setInt(3, publishPictureEvent.timestamp);
|
||||
statement.setString(4, publishPictureEvent.URL);
|
||||
statement.execute();
|
||||
|
||||
this.client.getHabbo().getHabboInfo().setWebPublishTimestamp(timestamp);
|
||||
this.client.getHabbo().givePoints(CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS_TYPE, -CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS);
|
||||
|
||||
isOk = true;
|
||||
habboInfo.setWebPublishTimestamp(currentTimestamp);
|
||||
habbo.givePoints(CAMERA_PUBLISH_POINTS_TYPE, -CAMERA_PUBLISH_POINTS);
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
}
|
||||
this.client.sendResponse(new CameraPublishWaitMessageComposer(true, 0, ""));
|
||||
}
|
||||
|
||||
this.client.sendResponse(new CameraPublishWaitMessageComposer(isOk, cooldownLeft, isOk ? this.client.getHabbo().getHabboInfo().getPhotoURL() : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-20
@@ -2,6 +2,9 @@ package com.eu.habbo.messages.incoming.camera;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.camera.CameraPurchaseSuccesfullComposer;
|
||||
@@ -11,47 +14,53 @@ import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
|
||||
import com.eu.habbo.plugin.events.users.UserPurchasePictureEvent;
|
||||
|
||||
public class CameraPurchaseEvent extends MessageHandler {
|
||||
public static int CAMERA_PURCHASE_CREDITS = 5;
|
||||
public static int CAMERA_PURCHASE_POINTS = 5;
|
||||
public static int CAMERA_PURCHASE_POINTS_TYPE = 0;
|
||||
public static int CAMERA_PURCHASE_CREDITS = 2;
|
||||
public static int CAMERA_PURCHASE_POINTS = 0;
|
||||
public static int CAMERA_PURCHASE_POINTS_TYPE = 5;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (this.client.getHabbo().getHabboInfo().getCredits() < CameraPurchaseEvent.CAMERA_PURCHASE_CREDITS) {
|
||||
public void handle() {
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
HabboInfo habboInfo = habbo.getHabboInfo();
|
||||
|
||||
if (habboInfo.getCredits() < CAMERA_PURCHASE_CREDITS) {
|
||||
habbo.alert("You don't have enough credits!");
|
||||
this.client.sendResponse(new NotEnoughPointsTypeComposer(true, false, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.client.getHabbo().getHabboInfo().getCurrencyAmount(CameraPurchaseEvent.CAMERA_PURCHASE_POINTS_TYPE) < CameraPurchaseEvent.CAMERA_PURCHASE_POINTS) {
|
||||
this.client.sendResponse(new NotEnoughPointsTypeComposer(false, true, CameraPurchaseEvent.CAMERA_PURCHASE_POINTS_TYPE));
|
||||
if (habboInfo.getCurrencyAmount(CAMERA_PURCHASE_POINTS_TYPE) < CAMERA_PURCHASE_POINTS) {
|
||||
String alertMessage = "You don't have enough " + Emulator.getTexts().getValue("seasonal.name." + CAMERA_PURCHASE_POINTS_TYPE, "currency") + "!";
|
||||
habbo.alert(alertMessage);
|
||||
this.client.sendResponse(new NotEnoughPointsTypeComposer(false, true, CAMERA_PURCHASE_POINTS_TYPE));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.client.getHabbo().getHabboInfo().getPhotoTimestamp() == 0) return;
|
||||
if (this.client.getHabbo().getHabboInfo().getPhotoJSON().isEmpty()) return;
|
||||
if (!this.client.getHabbo().getHabboInfo().getPhotoJSON().contains(this.client.getHabbo().getHabboInfo().getPhotoTimestamp() + ""))
|
||||
if (habboInfo.getPhotoTimestamp() == 0 || habboInfo.getPhotoJSON().isEmpty()
|
||||
|| !habboInfo.getPhotoJSON().contains(Integer.toString(habboInfo.getPhotoTimestamp())))
|
||||
return;
|
||||
|
||||
if (Emulator.getPluginManager().fireEvent(new UserPurchasePictureEvent(this.client.getHabbo(), this.client.getHabbo().getHabboInfo().getPhotoURL(), this.client.getHabbo().getHabboInfo().getCurrentRoom().getId(), this.client.getHabbo().getHabboInfo().getPhotoTimestamp())).isCancelled()) {
|
||||
if (Emulator.getPluginManager().fireEvent(new UserPurchasePictureEvent(habbo, habboInfo.getPhotoURL(), habboInfo.getCurrentRoom().getId(), habboInfo.getPhotoTimestamp())).isCancelled())
|
||||
return;
|
||||
}
|
||||
|
||||
HabboItem photoItem = Emulator.getGameEnvironment().getItemManager().createItem(this.client.getHabbo().getHabboInfo().getId(), Emulator.getGameEnvironment().getItemManager().getItem(Emulator.getConfig().getInt("camera.item_id")), 0, 0, this.client.getHabbo().getHabboInfo().getPhotoJSON());
|
||||
Item item = Emulator.getGameEnvironment().getItemManager().getItem(Emulator.getConfig().getInt("camera.item_id"));
|
||||
if (item == null || !item.getInteractionType().getName().equals("external_image"))
|
||||
return;
|
||||
|
||||
HabboItem photoItem = Emulator.getGameEnvironment().getItemManager().createItem(habboInfo.getId(), item, 0, 0, habboInfo.getPhotoJSON());
|
||||
if (photoItem != null) {
|
||||
photoItem.setExtradata(photoItem.getExtradata().replace("%id%", photoItem.getId() + ""));
|
||||
photoItem.setExtradata(photoItem.getExtradata().replace("%id%", Integer.toString(photoItem.getId())));
|
||||
photoItem.needsUpdate(true);
|
||||
|
||||
this.client.getHabbo().getInventory().getItemsComponent().addItem(photoItem);
|
||||
habbo.getInventory().getItemsComponent().addItem(photoItem);
|
||||
|
||||
this.client.sendResponse(new CameraPurchaseSuccesfullComposer());
|
||||
this.client.sendResponse(new AddHabboItemComposer(photoItem));
|
||||
this.client.sendResponse(new InventoryRefreshComposer());
|
||||
|
||||
this.client.getHabbo().giveCredits(-CameraPurchaseEvent.CAMERA_PURCHASE_CREDITS);
|
||||
this.client.getHabbo().givePoints(CameraPurchaseEvent.CAMERA_PURCHASE_POINTS_TYPE, -CameraPurchaseEvent.CAMERA_PURCHASE_POINTS);
|
||||
habbo.giveCredits(-CAMERA_PURCHASE_CREDITS);
|
||||
habbo.givePoints(CAMERA_PURCHASE_POINTS_TYPE, -CAMERA_PURCHASE_POINTS);
|
||||
|
||||
AchievementManager.progressAchievement(this.client.getHabbo(), Emulator.getGameEnvironment().getAchievementManager().getAchievement("CameraPhotoCount"));
|
||||
AchievementManager.progressAchievement(habbo, Emulator.getGameEnvironment().getAchievementManager().getAchievement("CameraPhotoCount"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+211
-21
@@ -1,37 +1,227 @@
|
||||
package com.eu.habbo.messages.incoming.camera;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||
import com.eu.habbo.habbohotel.users.HabboStats;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.networking.camera.CameraClient;
|
||||
import com.eu.habbo.networking.camera.messages.outgoing.CameraRenderImageComposer;
|
||||
import com.eu.habbo.util.crypto.ZIP;
|
||||
import com.eu.habbo.messages.outgoing.camera.CameraURLComposer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CameraRoomPictureEvent extends MessageHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraRoomPictureEvent.class);
|
||||
|
||||
public static int CAMERA_RENDER_DELAY = 15;
|
||||
public static int MAX_IMAGE_BYTES = 2 * 1024 * 1024; // 2 MB max upload
|
||||
public static int MAX_IMAGE_WIDTH = 1024;
|
||||
public static int MAX_IMAGE_HEIGHT = 1024;
|
||||
public static int MAX_DAILY_RENDERS = 50;
|
||||
|
||||
private ByteBuf image = null;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission("acc_camera")) {
|
||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("camera.permission"));
|
||||
public void handle() {
|
||||
try {
|
||||
this.make();
|
||||
} finally {
|
||||
if (this.image != null) {
|
||||
this.image.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void make() {
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
if (!habbo.hasPermission("acc_camera")) {
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (CameraClient.isLoggedIn) {
|
||||
this.packet.getBuffer().readFloat();
|
||||
HabboInfo habboInfo = habbo.getHabboInfo();
|
||||
HabboStats habboStats = habbo.getHabboStats();
|
||||
int timestamp = Emulator.getIntUnixTimestamp();
|
||||
|
||||
byte[] data = this.packet.getBuffer().readBytes(this.packet.getBuffer().readableBytes()).array();
|
||||
|
||||
String content = new String(ZIP.inflate(data));
|
||||
CameraRenderImageComposer composer = new CameraRenderImageComposer(this.client.getHabbo().getHabboInfo().getId(), this.client.getHabbo().getHabboInfo().getCurrentRoom().getBackgroundTonerColor().getRGB(), 320, 320, content);
|
||||
this.client.getHabbo().getHabboInfo().setPhotoJSON(Emulator.getConfig().getValue("camera.extradata").replace("%timestamp%", composer.timestamp + ""));
|
||||
this.client.getHabbo().getHabboInfo().setPhotoTimestamp(composer.timestamp);
|
||||
|
||||
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) {
|
||||
this.client.getHabbo().getHabboInfo().setPhotoRoomId(this.client.getHabbo().getHabboInfo().getCurrentRoom().getId());
|
||||
if (habboStats.cache.containsKey("camera_render_cooldown")) {
|
||||
int cameraTimestamp = (Integer) habboStats.cache.get("camera_render_cooldown");
|
||||
if (timestamp - cameraTimestamp < CAMERA_RENDER_DELAY) {
|
||||
String alertMessage = Emulator.getTexts().getValue("camera.wait").replace("%seconds%", Integer.toString(CAMERA_RENDER_DELAY - (timestamp - cameraTimestamp)));
|
||||
habbo.alert(alertMessage);
|
||||
if (habboInfo.getPhotoURL() != null) {
|
||||
String[] splittedPhotoURL = habboInfo.getPhotoURL().split("/");
|
||||
if (splittedPhotoURL.length > 0) {
|
||||
this.client.sendResponse(new CameraURLComposer(splittedPhotoURL[splittedPhotoURL.length - 1]));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Emulator.getCameraClient().sendMessage(composer);
|
||||
} else {
|
||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("camera.disabled"));
|
||||
}
|
||||
|
||||
// Daily render quota check
|
||||
int dailyRenderCount = getDailyRenderCount(habboStats, timestamp);
|
||||
if (dailyRenderCount >= MAX_DAILY_RENDERS) {
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.daily.limit", "You have reached the daily photo limit. Try again tomorrow."));
|
||||
return;
|
||||
}
|
||||
incrementDailyRenderCount(habboStats, timestamp, dailyRenderCount);
|
||||
|
||||
habboStats.cache.put("camera_render_cooldown", timestamp);
|
||||
Room room = habboInfo.getCurrentRoom();
|
||||
if (room == null) return;
|
||||
|
||||
int count = this.packet.readInt();
|
||||
|
||||
// Reject oversized payloads before reading
|
||||
if (count <= 0 || count > MAX_IMAGE_BYTES) {
|
||||
LOGGER.warn("User {} attempted camera upload with invalid size: {} bytes", habboInfo.getUsername(), count);
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.image = this.packet.getBuffer().readBytes(count);
|
||||
if (this.image == null) return;
|
||||
|
||||
byte[] imageBytes = ByteBufUtil.getBytes(this.image, 0, 4, true);
|
||||
if (imageBytes == null || imageBytes.length < 4 || !isPNG(imageBytes)) {
|
||||
LOGGER.warn("User {} attempted camera upload with non-PNG data", habboInfo.getUsername());
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate image dimensions before fully decoding
|
||||
int[] dimensions;
|
||||
try {
|
||||
dimensions = readPNGDimensions(this.image);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("User {} uploaded image with unreadable dimensions", habboInfo.getUsername());
|
||||
handleImageProcessingError(habbo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dimensions == null || dimensions[0] <= 0 || dimensions[1] <= 0
|
||||
|| dimensions[0] > MAX_IMAGE_WIDTH || dimensions[1] > MAX_IMAGE_HEIGHT) {
|
||||
LOGGER.warn("User {} attempted camera upload with invalid dimensions: {}x{}",
|
||||
habboInfo.getUsername(),
|
||||
dimensions != null ? dimensions[0] : "null",
|
||||
dimensions != null ? dimensions[1] : "null");
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage theImage;
|
||||
try (ByteBufInputStream in = new ByteBufInputStream(this.image)) {
|
||||
theImage = ImageIO.read(in);
|
||||
} catch (IOException e) {
|
||||
handleImageProcessingError(habbo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (theImage == null) {
|
||||
LOGGER.warn("User {} uploaded image that could not be decoded", habboInfo.getUsername());
|
||||
handleImageProcessingError(habbo);
|
||||
return;
|
||||
}
|
||||
|
||||
// Double-check decoded dimensions match expectations
|
||||
if (theImage.getWidth() > MAX_IMAGE_WIDTH || theImage.getHeight() > MAX_IMAGE_HEIGHT) {
|
||||
LOGGER.warn("User {} decoded image exceeds dimension limits: {}x{}", habboInfo.getUsername(), theImage.getWidth(), theImage.getHeight());
|
||||
handleImageProcessingError(habbo);
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName = habboInfo.getId() + "_" + timestamp;
|
||||
String URL = fileName + ".png";
|
||||
String URLsmall = fileName + "_small.png";
|
||||
String base = Emulator.getConfig().getValue("camera.url");
|
||||
String json = Emulator.getConfig().getValue("camera.extradata")
|
||||
.replace("%timestamp%", Integer.toString(timestamp))
|
||||
.replace("%room_id%", Integer.toString(room.getId()))
|
||||
.replace("%url%", base + URL);
|
||||
habboInfo.setPhotoURL(base + URL);
|
||||
habboInfo.setPhotoTimestamp(timestamp);
|
||||
habboInfo.setPhotoRoomId(room.getId());
|
||||
habboInfo.setPhotoJSON(json);
|
||||
|
||||
File imageFile = new File(Emulator.getConfig().getValue("imager.location.output.camera") + URL);
|
||||
File smallImageFile = new File(Emulator.getConfig().getValue("imager.location.output.camera") + URLsmall);
|
||||
|
||||
try {
|
||||
ImageIO.write(theImage, "png", imageFile);
|
||||
int smallWidth = theImage.getWidth(null) / 2;
|
||||
int smallHeight = theImage.getHeight(null) / 2;
|
||||
BufferedImage bi = new BufferedImage(smallWidth, smallHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics2D = bi.createGraphics();
|
||||
graphics2D.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION, java.awt.RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||
graphics2D.drawImage(theImage, 0, 0, smallWidth, smallHeight, null);
|
||||
graphics2D.dispose();
|
||||
ImageIO.write(bi, "png", smallImageFile);
|
||||
} catch (IOException e) {
|
||||
handleImageProcessingError(habbo);
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new CameraURLComposer(URL));
|
||||
}
|
||||
|
||||
private boolean isPNG(byte[] bytes) {
|
||||
return bytes[0] == (byte) 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read PNG dimensions from the IHDR chunk without fully decoding the image.
|
||||
* This prevents decompression bomb attacks by checking dimensions before allocation.
|
||||
*/
|
||||
private int[] readPNGDimensions(ByteBuf buf) throws IOException {
|
||||
try (ByteBufInputStream in = new ByteBufInputStream(buf.duplicate())) {
|
||||
try (ImageInputStream iis = ImageIO.createImageInputStream(in)) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
|
||||
if (!readers.hasNext()) return null;
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
try {
|
||||
reader.setInput(iis);
|
||||
int width = reader.getWidth(0);
|
||||
int height = reader.getHeight(0);
|
||||
return new int[]{width, height};
|
||||
} finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getDailyRenderCount(HabboStats stats, int currentTimestamp) {
|
||||
if (!stats.cache.containsKey("camera_daily_count") || !stats.cache.containsKey("camera_daily_reset")) {
|
||||
return 0;
|
||||
}
|
||||
int resetTimestamp = (Integer) stats.cache.get("camera_daily_reset");
|
||||
// Reset counter if more than 24 hours have passed
|
||||
if (currentTimestamp - resetTimestamp >= 86400) {
|
||||
return 0;
|
||||
}
|
||||
return (Integer) stats.cache.get("camera_daily_count");
|
||||
}
|
||||
|
||||
private void incrementDailyRenderCount(HabboStats stats, int currentTimestamp, int currentCount) {
|
||||
if (currentCount == 0) {
|
||||
stats.cache.put("camera_daily_reset", currentTimestamp);
|
||||
}
|
||||
stats.cache.put("camera_daily_count", currentCount + 1);
|
||||
}
|
||||
|
||||
private void handleImageProcessingError(Habbo habbo) {
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
}
|
||||
}
|
||||
|
||||
+157
-27
@@ -1,37 +1,167 @@
|
||||
package com.eu.habbo.messages.incoming.camera;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||
import com.eu.habbo.habbohotel.users.HabboStats;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.camera.CameraRoomThumbnailSavedComposer;
|
||||
import com.eu.habbo.networking.camera.CameraClient;
|
||||
import com.eu.habbo.networking.camera.messages.outgoing.CameraRenderImageComposer;
|
||||
import com.eu.habbo.util.crypto.ZIP;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CameraRoomThumbnailEvent extends MessageHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraRoomThumbnailEvent.class);
|
||||
|
||||
public static int CAMERA_RENDER_DELAY = 15;
|
||||
public static int MAX_THUMBNAIL_BYTES = 1024 * 1024; // 1 MB max for thumbnails
|
||||
public static int MAX_THUMBNAIL_WIDTH = 640;
|
||||
public static int MAX_THUMBNAIL_HEIGHT = 640;
|
||||
|
||||
private ByteBuf image = null;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission("acc_camera")) {
|
||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("camera.permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.client.getHabbo().getHabboInfo().getCurrentRoom().isOwner(this.client.getHabbo()))
|
||||
return;
|
||||
|
||||
if (CameraClient.isLoggedIn) {
|
||||
this.packet.getBuffer().readFloat();
|
||||
byte[] data = this.packet.getBuffer().readBytes(this.packet.getBuffer().readableBytes()).array();
|
||||
String content = new String(ZIP.inflate(data));
|
||||
|
||||
CameraRenderImageComposer composer = new CameraRenderImageComposer(this.client.getHabbo().getHabboInfo().getId(), this.client.getHabbo().getHabboInfo().getCurrentRoom().getBackgroundTonerColor().getRGB(), 110, 110, content);
|
||||
|
||||
this.client.getHabbo().getHabboInfo().setPhotoJSON(Emulator.getConfig().getValue("camera.extradata").replace("%timestamp%", composer.timestamp + ""));
|
||||
this.client.getHabbo().getHabboInfo().setPhotoTimestamp(composer.timestamp);
|
||||
|
||||
Emulator.getCameraClient().sendMessage(composer);
|
||||
} else {
|
||||
this.client.sendResponse(new CameraRoomThumbnailSavedComposer());
|
||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("camera.disabled"));
|
||||
public void handle() {
|
||||
try {
|
||||
this.make();
|
||||
} finally {
|
||||
if (this.image != null) {
|
||||
this.image.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void make() {
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
if (!habbo.hasPermission("acc_camera")) {
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
HabboStats habboStats = habbo.getHabboStats();
|
||||
int timestamp = Emulator.getIntUnixTimestamp();
|
||||
|
||||
if (habboStats.cache.containsKey("camera_render_cooldown")) {
|
||||
int cameraTimestamp = (Integer) habboStats.cache.get("camera_render_cooldown");
|
||||
if (timestamp - cameraTimestamp < CAMERA_RENDER_DELAY) {
|
||||
String alertMessage = Emulator.getTexts().getValue("camera.wait").replace("%seconds%", Integer.toString(CAMERA_RENDER_DELAY - (timestamp - cameraTimestamp)));
|
||||
habbo.alert(alertMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
habboStats.cache.put("camera_render_cooldown", timestamp);
|
||||
HabboInfo habboInfo = habbo.getHabboInfo();
|
||||
Room room = habboInfo.getCurrentRoom();
|
||||
if (room == null || !room.isOwner(habbo)) return;
|
||||
|
||||
int count = this.packet.readInt();
|
||||
|
||||
// Reject oversized payloads before reading
|
||||
if (count <= 0 || count > MAX_THUMBNAIL_BYTES) {
|
||||
LOGGER.warn("User {} attempted thumbnail upload with invalid size: {} bytes", habboInfo.getUsername(), count);
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.image = this.packet.getBuffer().readBytes(count);
|
||||
if (this.image == null || !isValidImage(this.image)) {
|
||||
LOGGER.warn("User {} attempted thumbnail upload with non-PNG data", habboInfo.getUsername());
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate dimensions before fully decoding (prevents decompression bombs)
|
||||
int[] dimensions;
|
||||
try {
|
||||
dimensions = readPNGDimensions(this.image);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("User {} uploaded thumbnail with unreadable dimensions", habboInfo.getUsername());
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (dimensions == null || dimensions[0] <= 0 || dimensions[1] <= 0
|
||||
|| dimensions[0] > MAX_THUMBNAIL_WIDTH || dimensions[1] > MAX_THUMBNAIL_HEIGHT) {
|
||||
LOGGER.warn("User {} attempted thumbnail upload with invalid dimensions: {}x{}",
|
||||
habboInfo.getUsername(),
|
||||
dimensions != null ? dimensions[0] : "null",
|
||||
dimensions != null ? dimensions[1] : "null");
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage theImage;
|
||||
try (ByteBufInputStream in = new ByteBufInputStream(this.image)) {
|
||||
theImage = ImageIO.read(in);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to decode thumbnail from user {}", habboInfo.getUsername(), e);
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (theImage == null) {
|
||||
LOGGER.warn("User {} uploaded thumbnail that could not be decoded", habboInfo.getUsername());
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Double-check decoded dimensions
|
||||
if (theImage.getWidth() > MAX_THUMBNAIL_WIDTH || theImage.getHeight() > MAX_THUMBNAIL_HEIGHT) {
|
||||
LOGGER.warn("User {} decoded thumbnail exceeds dimension limits: {}x{}", habboInfo.getUsername(), theImage.getWidth(), theImage.getHeight());
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
File imageFile = new File(Emulator.getConfig().getValue("imager.location.output.thumbnail") + room.getId() + ".png");
|
||||
try {
|
||||
ImageIO.write(theImage, "png", imageFile);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write thumbnail for room {}", room.getId(), e);
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new CameraRoomThumbnailSavedComposer());
|
||||
}
|
||||
|
||||
private boolean isValidImage(ByteBuf imageBuffer) {
|
||||
byte[] imageBytes = ByteBufUtil.getBytes(imageBuffer, 0, 4, true);
|
||||
return imageBytes != null && imageBytes.length >= 4
|
||||
&& imageBytes[0] == (byte) 0x89 && imageBytes[1] == 0x50
|
||||
&& imageBytes[2] == 0x4E && imageBytes[3] == 0x47;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read PNG dimensions from the IHDR chunk without fully decoding the image.
|
||||
* This prevents decompression bomb attacks by checking dimensions before allocation.
|
||||
*/
|
||||
private int[] readPNGDimensions(ByteBuf buf) throws IOException {
|
||||
try (ByteBufInputStream in = new ByteBufInputStream(buf.duplicate())) {
|
||||
try (ImageInputStream iis = ImageIO.createImageInputStream(in)) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
|
||||
if (!readers.hasNext()) return null;
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
try {
|
||||
reader.setInput(iis);
|
||||
int width = reader.getWidth(0);
|
||||
int height = reader.getHeight(0);
|
||||
return new int[]{width, height};
|
||||
} finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,11 @@ public class RoomSettingsSaveEvent extends MessageHandler {
|
||||
room.setChatSpeed(this.packet.readInt());
|
||||
room.setChatDistance(Math.abs(this.packet.readInt()));
|
||||
room.setChatProtection(this.packet.readInt());
|
||||
|
||||
if (this.packet.bytesAvailable() > 0) {
|
||||
room.setAllowUnderpass(this.packet.readBoolean());
|
||||
}
|
||||
|
||||
room.setNeedsUpdate(true);
|
||||
|
||||
room.sendComposer(new RoomThicknessComposer(room).compose());
|
||||
|
||||
@@ -52,6 +52,7 @@ public class RoomSettingsComposer extends MessageComposer {
|
||||
this.response.appendInt(this.room.getMuteOption());
|
||||
this.response.appendInt(this.room.getKickOption());
|
||||
this.response.appendInt(this.room.getBanOption());
|
||||
this.response.appendInt(this.room.isAllowUnderpass() ? 1 : 0);
|
||||
return this.response;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import com.eu.habbo.networking.camera.messages.outgoing.CameraLoginComposer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CameraClient {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraClient.class);
|
||||
|
||||
private static final String host = "google.com";
|
||||
private static final int port = 1232;
|
||||
public static ChannelFuture channelFuture;
|
||||
public static boolean isLoggedIn = false;
|
||||
public static boolean attemptReconnect = true;
|
||||
private static Channel channel;
|
||||
private final Bootstrap bootstrap = new Bootstrap();
|
||||
|
||||
public CameraClient() {
|
||||
|
||||
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
|
||||
this.bootstrap.group(eventLoopGroup);
|
||||
this.bootstrap.channel(NioSocketChannel.class);
|
||||
this.bootstrap.option(ChannelOption.TCP_NODELAY, true);
|
||||
this.bootstrap.option(ChannelOption.SO_KEEPALIVE, false);
|
||||
this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(new CameraDecoder());
|
||||
ch.pipeline().addLast(new CameraHandler());
|
||||
}
|
||||
});
|
||||
this.bootstrap.option(ChannelOption.SO_RCVBUF, 5120);
|
||||
this.bootstrap.option(ChannelOption.SO_REUSEADDR, true);
|
||||
this.bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(5120));
|
||||
this.bootstrap.option(ChannelOption.ALLOCATOR, new UnpooledByteBufAllocator(false));
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
CameraClient.channelFuture = this.bootstrap.connect(host, port);
|
||||
|
||||
while (!CameraClient.channelFuture.isDone()) {
|
||||
}
|
||||
|
||||
if (CameraClient.channelFuture.isSuccess()) {
|
||||
CameraClient.attemptReconnect = false;
|
||||
CameraClient.channel = channelFuture.channel();
|
||||
LOGGER.info("Connected to the Camera Server. Attempting to login.");
|
||||
this.sendMessage(new CameraLoginComposer());
|
||||
} else {
|
||||
LOGGER.error("Failed to connect to the Camera Server. Server unreachable.");
|
||||
CameraClient.channel = null;
|
||||
CameraClient.channelFuture.channel().close();
|
||||
CameraClient.channelFuture = null;
|
||||
CameraClient.attemptReconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (channelFuture != null) {
|
||||
try {
|
||||
channelFuture.channel().close().sync();
|
||||
channelFuture = null;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
channel = null;
|
||||
isLoggedIn = false;
|
||||
|
||||
LOGGER.info("Disconnected from the camera server.");
|
||||
}
|
||||
|
||||
public void sendMessage(CameraOutgoingMessage outgoingMessage) {
|
||||
try {
|
||||
if (isLoggedIn || outgoingMessage instanceof CameraLoginComposer) {
|
||||
outgoingMessage.compose(channel);
|
||||
channel.write(outgoingMessage.get().copy(), channel.voidPromise());
|
||||
channel.flush();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class CameraDecoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> objects) {
|
||||
int readerIndex = byteBuf.readerIndex();
|
||||
if (byteBuf.readableBytes() < 6) {
|
||||
byteBuf.readerIndex(readerIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
int length = byteBuf.readInt();
|
||||
byteBuf.readerIndex(readerIndex);
|
||||
|
||||
if (byteBuf.readableBytes() < (length)) {
|
||||
byteBuf.readerIndex(readerIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
byteBuf.readerIndex(readerIndex);
|
||||
objects.add(byteBuf.readBytes(length + 4));
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
public class CameraHandler extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
try {
|
||||
ByteBuf message = (ByteBuf) msg;
|
||||
((ByteBuf) msg).readerIndex(0);
|
||||
int length = message.readInt();
|
||||
|
||||
ByteBuf b = Unpooled.wrappedBuffer(message.readBytes(length));
|
||||
|
||||
short header = b.readShort();
|
||||
|
||||
try {
|
||||
CameraPacketHandler.instance().handle(ctx.channel(), header, b);
|
||||
} catch (Exception e) {
|
||||
|
||||
} finally {
|
||||
try {
|
||||
|
||||
b.release();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
try {
|
||||
|
||||
((ByteBuf) msg).release();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
CameraClient.attemptReconnect = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public abstract class CameraIncomingMessage extends CameraMessage {
|
||||
public CameraIncomingMessage(Short header, ByteBuf body) {
|
||||
super(header);
|
||||
this.buffer.writerIndex(0).writeBytes(body);
|
||||
}
|
||||
|
||||
public int readShort() {
|
||||
return this.buffer.readShort();
|
||||
}
|
||||
|
||||
public Integer readInt() {
|
||||
try {
|
||||
return this.buffer.readInt();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean readBoolean() {
|
||||
try {
|
||||
return this.buffer.readByte() == 1;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public String readString() {
|
||||
try {
|
||||
int length = this.readInt();
|
||||
byte[] data = new byte[length];
|
||||
this.buffer.readBytes(data);
|
||||
return new String(data);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String getMessageBody() {
|
||||
String consoleText = this.buffer.toString(Charset.defaultCharset());
|
||||
|
||||
for (int i = -1; i < 31; i++) {
|
||||
consoleText = consoleText.replace(Character.toString((char) i), "[" + i + "]");
|
||||
}
|
||||
|
||||
return consoleText;
|
||||
}
|
||||
|
||||
public int bytesAvailable() {
|
||||
return this.buffer.readableBytes();
|
||||
}
|
||||
|
||||
public abstract void handle(Channel client) throws Exception;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class CameraMessage {
|
||||
protected final short header;
|
||||
protected final ByteBuf buffer;
|
||||
|
||||
public CameraMessage(short header) {
|
||||
this.header = header;
|
||||
this.buffer = Unpooled.buffer();
|
||||
}
|
||||
|
||||
public short getHeader() {
|
||||
return this.header;
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public abstract class CameraOutgoingMessage extends CameraMessage {
|
||||
private final ByteBufOutputStream stream;
|
||||
|
||||
public CameraOutgoingMessage(short header) {
|
||||
super(header);
|
||||
|
||||
this.stream = new ByteBufOutputStream(this.buffer);
|
||||
try {
|
||||
this.stream.writeInt(0);
|
||||
this.stream.writeShort(header);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendRawBytes(byte[] bytes) {
|
||||
try {
|
||||
this.stream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendString(String obj) {
|
||||
try {
|
||||
byte[] data = obj.getBytes();
|
||||
this.stream.writeInt(data.length);
|
||||
this.stream.write(data);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendChar(int obj) {
|
||||
try {
|
||||
this.stream.writeChar(obj);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendChars(Object obj) {
|
||||
try {
|
||||
this.stream.writeChars(obj.toString());
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendInt32(Integer obj) {
|
||||
try {
|
||||
this.stream.writeInt(obj);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendInt32(Byte obj) {
|
||||
try {
|
||||
this.stream.writeInt((int) obj);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendInt32(Boolean obj) {
|
||||
try {
|
||||
this.stream.writeInt(obj ? 1 : 0);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendShort(int obj) {
|
||||
try {
|
||||
this.stream.writeShort((short) obj);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendByte(Integer b) {
|
||||
try {
|
||||
this.stream.writeByte(b);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void appendBoolean(Boolean obj) {
|
||||
try {
|
||||
this.stream.writeBoolean(obj);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public CameraOutgoingMessage appendResponse(CameraOutgoingMessage obj) {
|
||||
try {
|
||||
this.stream.write(obj.get().array());
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getBodyString() {
|
||||
ByteBuf buffer = this.stream.buffer().duplicate();
|
||||
|
||||
buffer.setInt(0, buffer.writerIndex() - 4);
|
||||
|
||||
String consoleText = buffer.toString(Charset.forName("UTF-8"));
|
||||
|
||||
for (int i = 0; i < 14; i++) {
|
||||
consoleText = consoleText.replace(Character.toString((char) i), "[" + i + "]");
|
||||
}
|
||||
|
||||
buffer.discardSomeReadBytes();
|
||||
|
||||
return consoleText;
|
||||
}
|
||||
|
||||
public ByteBuf get() {
|
||||
this.buffer.setInt(0, this.buffer.writerIndex() - 4);
|
||||
|
||||
return this.buffer.copy();
|
||||
}
|
||||
|
||||
public abstract void compose(Channel channel);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.eu.habbo.networking.camera;
|
||||
|
||||
import com.eu.habbo.networking.camera.messages.incoming.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CameraPacketHandler {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraPacketHandler.class);
|
||||
|
||||
private static CameraPacketHandler INSTANCE;
|
||||
private final HashMap<Short, Class<? extends CameraIncomingMessage>> packetDefinitions;
|
||||
|
||||
public CameraPacketHandler() {
|
||||
this.packetDefinitions = new HashMap<>();
|
||||
|
||||
this.packetDefinitions.put((short) 1, CameraLoginStatusEvent.class);
|
||||
this.packetDefinitions.put((short) 2, CameraResultURLEvent.class);
|
||||
this.packetDefinitions.put((short) 3, CameraRoomThumbnailGeneratedEvent.class);
|
||||
this.packetDefinitions.put((short) 4, CameraUpdateNotification.class);
|
||||
this.packetDefinitions.put((short) 5, CameraAuthenticationTicketEvent.class);
|
||||
}
|
||||
|
||||
public static CameraPacketHandler instance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new CameraPacketHandler();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void handle(Channel channel, short i, ByteBuf ii) {
|
||||
Class<? extends CameraIncomingMessage> declaredClass = this.packetDefinitions.get(i);
|
||||
|
||||
if (declaredClass != null) {
|
||||
try {
|
||||
CameraIncomingMessage message = declaredClass.getDeclaredConstructor(new Class[]{Short.class, ByteBuf.class}).newInstance(i, ii);
|
||||
message.handle(channel);
|
||||
message.buffer.release();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Caught exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages;
|
||||
|
||||
public class CameraOutgoingHeaders {
|
||||
public final static short LoginComposer = 1;
|
||||
public final static short RenderImageComposer = 2;
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.incoming;
|
||||
|
||||
import com.eu.habbo.messages.outgoing.gamecenter.basejump.BaseJumpLoadGameComposer;
|
||||
import com.eu.habbo.networking.camera.CameraIncomingMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public class CameraAuthenticationTicketEvent extends CameraIncomingMessage {
|
||||
public CameraAuthenticationTicketEvent(Short header, ByteBuf body) {
|
||||
super(header, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Channel client) throws Exception {
|
||||
String ticket = this.readString();
|
||||
|
||||
if (ticket.startsWith("FASTFOOD")) {
|
||||
BaseJumpLoadGameComposer.FASTFOOD_KEY = ticket;
|
||||
}
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.incoming;
|
||||
|
||||
import com.eu.habbo.networking.camera.CameraClient;
|
||||
import com.eu.habbo.networking.camera.CameraIncomingMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CameraLoginStatusEvent extends CameraIncomingMessage {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraLoginStatusEvent.class);
|
||||
|
||||
public final static int LOGIN_OK = 0;
|
||||
public final static int LOGIN_ERROR = 1;
|
||||
public final static int NO_ACCOUNT = 2;
|
||||
public final static int ALREADY_LOGGED_IN = 3;
|
||||
public final static int BANNED = 4;
|
||||
public final static int OLD_BUILD = 5;
|
||||
public final static int NO_CAMERA_SUBSCRIPTION = 6;
|
||||
|
||||
public CameraLoginStatusEvent(Short header, ByteBuf body) {
|
||||
super(header, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Channel client) throws Exception {
|
||||
int status = this.readInt();
|
||||
|
||||
if (status == LOGIN_ERROR) {
|
||||
LOGGER.error("Failed to login to Camera Server: Incorrect Details");
|
||||
} else if (status == NO_ACCOUNT) {
|
||||
LOGGER.error("Failed to login to Camera Server: No Account Found. Register for free on the Arcturus Forums! Visit http://arcturus.pw/");
|
||||
} else if (status == BANNED) {
|
||||
LOGGER.error("Sorry but you seem to be banned from the Arcturus forums and therefor cant use the Camera Server :'(");
|
||||
} else if (status == ALREADY_LOGGED_IN) {
|
||||
LOGGER.error("You seem to be already connected to the Camera Server");
|
||||
} else if (status == OLD_BUILD) {
|
||||
LOGGER.error("This version of Arcturus Emulator is no longer supported by the Camera Server. Upgrade your emulator.");
|
||||
} else if (status == NO_CAMERA_SUBSCRIPTION) {
|
||||
LOGGER.error("You don't have a Camera Subscription and therefor cannot use the camera!");
|
||||
LOGGER.error("Please consider making a donation to keep this project going. The emulator can be used free of charge!");
|
||||
LOGGER.error("A trial version is available for $2.5. A year subscription is only $10 and a permanent subscription is $25.");
|
||||
LOGGER.error("By donating this subscription you support the development of the emulator you are using :)");
|
||||
LOGGER.error("Visit http://arcturus.pw/mysubscriptions.php to buy your subscription!");
|
||||
LOGGER.error("Please Consider getting a subscription. Regards: The General");
|
||||
}
|
||||
|
||||
if (status == LOGIN_OK) {
|
||||
CameraClient.isLoggedIn = true;
|
||||
LOGGER.info("Succesfully connected to the Arcturus Camera Server!");
|
||||
} else {
|
||||
CameraClient.attemptReconnect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.incoming;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.outgoing.camera.CameraURLComposer;
|
||||
import com.eu.habbo.networking.camera.CameraIncomingMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public class CameraResultURLEvent extends CameraIncomingMessage {
|
||||
public final static int STATUS_OK = 0;
|
||||
public final static int STATUS_ERROR = 1;
|
||||
|
||||
public CameraResultURLEvent(Short header, ByteBuf body) {
|
||||
super(header, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Channel client) throws Exception {
|
||||
int userId = this.readInt();
|
||||
int status = this.readInt();
|
||||
String URL = this.readString();
|
||||
|
||||
if (!Emulator.getConfig().getBoolean("camera.use.https", true)) {
|
||||
URL = URL.replace("https://", "http://");
|
||||
}
|
||||
|
||||
int roomId = this.readInt();
|
||||
int timestamp = this.readInt();
|
||||
|
||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (status == STATUS_ERROR) {
|
||||
if (habbo != null) {
|
||||
habbo.getHabboInfo().setPhotoTimestamp(0);
|
||||
habbo.getHabboInfo().setPhotoJSON("");
|
||||
habbo.getHabboInfo().setPhotoURL("");
|
||||
|
||||
habbo.alert(Emulator.getTexts().getValue("camera.error.creation"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == STATUS_OK) {
|
||||
if (habbo != null) {
|
||||
if (timestamp == habbo.getHabboInfo().getPhotoTimestamp()) {
|
||||
AchievementManager.progressAchievement(habbo, Emulator.getGameEnvironment().getAchievementManager().getAchievement("CameraPhotoCount"), 1);
|
||||
habbo.getClient().sendResponse(new CameraURLComposer(URL));
|
||||
habbo.getHabboInfo().setPhotoJSON(habbo.getHabboInfo().getPhotoJSON().replace("%room_id%", roomId + "").replace("%url%", URL));
|
||||
habbo.getHabboInfo().setPhotoURL(URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.incoming;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.outgoing.camera.CameraRoomThumbnailSavedComposer;
|
||||
import com.eu.habbo.networking.camera.CameraIncomingMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public class CameraRoomThumbnailGeneratedEvent extends CameraIncomingMessage {
|
||||
public CameraRoomThumbnailGeneratedEvent(Short header, ByteBuf body) {
|
||||
super(header, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Channel client) throws Exception {
|
||||
int userId = this.readInt();
|
||||
|
||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||
|
||||
if (habbo != null) {
|
||||
habbo.getClient().sendResponse(new CameraRoomThumbnailSavedComposer());
|
||||
}
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.incoming;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
||||
import com.eu.habbo.networking.camera.CameraIncomingMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CameraUpdateNotification extends CameraIncomingMessage {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraUpdateNotification.class);
|
||||
|
||||
public CameraUpdateNotification(Short header, ByteBuf body) {
|
||||
super(header, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Channel client) throws Exception {
|
||||
boolean alert = this.readBoolean();
|
||||
String message = this.readString();
|
||||
int type = this.readInt();
|
||||
|
||||
if (type == 0) {
|
||||
LOGGER.info("Camera update: {}", message);
|
||||
} else if (type == 1) {
|
||||
LOGGER.warn("Camera update: {}", message);
|
||||
} else if (type == 2) {
|
||||
LOGGER.error("Camera update: {}", message);
|
||||
}
|
||||
|
||||
if (alert) {
|
||||
Emulator.getGameServer().getGameClientManager().sendBroadcastResponse(new GenericAlertComposer(message).compose());
|
||||
}
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.outgoing;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.networking.camera.CameraOutgoingMessage;
|
||||
import com.eu.habbo.networking.camera.messages.CameraOutgoingHeaders;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public class CameraLoginComposer extends CameraOutgoingMessage {
|
||||
public CameraLoginComposer() {
|
||||
super(CameraOutgoingHeaders.LoginComposer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(Channel channel) {
|
||||
this.appendString(Emulator.getConfig().getValue("username").trim());
|
||||
this.appendString(Emulator.getConfig().getValue("password").trim());
|
||||
this.appendString(Emulator.version);
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package com.eu.habbo.networking.camera.messages.outgoing;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.networking.camera.CameraOutgoingMessage;
|
||||
import com.eu.habbo.networking.camera.messages.CameraOutgoingHeaders;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public class CameraRenderImageComposer extends CameraOutgoingMessage {
|
||||
public final int timestamp;
|
||||
final int userId;
|
||||
final int backgroundColor;
|
||||
final int width;
|
||||
final int height;
|
||||
final String JSON;
|
||||
|
||||
public CameraRenderImageComposer(int userId, int backgroundColor, int width, int height, String json) {
|
||||
super(CameraOutgoingHeaders.RenderImageComposer);
|
||||
|
||||
this.userId = userId;
|
||||
this.timestamp = Emulator.getIntUnixTimestamp();
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.JSON = json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(Channel channel) {
|
||||
this.appendInt32(this.userId);
|
||||
this.appendInt32(this.timestamp);
|
||||
this.appendInt32(this.backgroundColor);
|
||||
this.appendInt32(this.width);
|
||||
this.appendInt32(this.height);
|
||||
this.appendString(this.JSON);
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ import com.eu.habbo.habbohotel.wired.core.WiredEngine;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreManager;
|
||||
import com.eu.habbo.messages.PacketManager;
|
||||
import com.eu.habbo.messages.incoming.camera.CameraPublishToWebEvent;
|
||||
import com.eu.habbo.messages.incoming.camera.CameraPurchaseEvent;
|
||||
import com.eu.habbo.messages.incoming.catalog.CheckPetNameEvent;
|
||||
import com.eu.habbo.messages.incoming.floorplaneditor.FloorPlanEditorSaveEvent;
|
||||
import com.eu.habbo.messages.incoming.hotelview.HotelViewRequestLTDAvailabilityEvent;
|
||||
@@ -161,11 +159,6 @@ public class PluginManager {
|
||||
|
||||
|
||||
ChangeNameCheckUsernameEvent.VALID_CHARACTERS = Emulator.getConfig().getValue("allowed.username.characters", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-=!?@:,.");
|
||||
CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS = Emulator.getConfig().getInt("camera.price.points.publish", 5);
|
||||
CameraPublishToWebEvent.CAMERA_PUBLISH_POINTS_TYPE = Emulator.getConfig().getInt("camera.price.points.publish.type", 0);
|
||||
CameraPurchaseEvent.CAMERA_PURCHASE_CREDITS = Emulator.getConfig().getInt("camera.price.credits", 5);
|
||||
CameraPurchaseEvent.CAMERA_PURCHASE_POINTS = Emulator.getConfig().getInt("camera.price.points", 5);
|
||||
CameraPurchaseEvent.CAMERA_PURCHASE_POINTS_TYPE = Emulator.getConfig().getInt("camera.price.points.type", 0);
|
||||
|
||||
BuyRoomPromotionEvent.ROOM_PROMOTION_BADGE = Emulator.getConfig().getValue("room.promotion.badge", "RADZZ");
|
||||
BotManager.MAXIMUM_BOT_INVENTORY_SIZE = Emulator.getConfig().getInt("hotel.bots.max.inventory");
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package com.eu.habbo.threading.runnables;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.networking.camera.CameraClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CameraClientAutoReconnect implements Runnable {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CameraClientAutoReconnect.class);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (CameraClient.attemptReconnect && !Emulator.isShuttingDown) {
|
||||
if (!(CameraClient.channelFuture != null && CameraClient.channelFuture.channel().isRegistered())) {
|
||||
LOGGER.info("Attempting to connect to the Camera server.");
|
||||
if (Emulator.getCameraClient() != null) {
|
||||
Emulator.getCameraClient().disconnect();
|
||||
} else {
|
||||
Emulator.setCameraClient(new CameraClient());
|
||||
}
|
||||
|
||||
try {
|
||||
Emulator.getCameraClient().connect();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to start the camera client.", e);
|
||||
}
|
||||
} else {
|
||||
CameraClient.attemptReconnect = false;
|
||||
LOGGER.info("Already connected to the camera. Reconnecting not needed!");
|
||||
}
|
||||
}
|
||||
|
||||
Emulator.getThreading().run(this, 5000);
|
||||
}
|
||||
}
|
||||
@@ -4,39 +4,56 @@ import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTileState;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer;
|
||||
import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer;
|
||||
import com.eu.habbo.util.pathfinding.Direction8;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
/**
|
||||
* Alternative football physics based on the Rebug plugin.
|
||||
* Uses momentum decay (ball slows down over time) and simple 180-degree bounce.
|
||||
*/
|
||||
public class RebugKickBallAction implements Runnable {
|
||||
|
||||
private final HabboItem ball;
|
||||
private final Room room;
|
||||
private Direction8 direction;
|
||||
private int momentum;
|
||||
private boolean isDribble;
|
||||
public boolean dead = false;
|
||||
|
||||
public RebugKickBallAction(HabboItem ball, Room room, RoomUnit kicker, boolean hasPath) {
|
||||
private final boolean zigzag;
|
||||
private Direction8 zigzagA;
|
||||
private Direction8 zigzagB;
|
||||
private boolean zigzagSide = false;
|
||||
|
||||
private int tilesSinceBounce = -1;
|
||||
|
||||
public RebugKickBallAction(HabboItem ball, Room room, Direction8 direction, int momentum) {
|
||||
this(ball, room, direction, momentum, false);
|
||||
}
|
||||
|
||||
public RebugKickBallAction(HabboItem ball, Room room, Direction8 direction, int momentum, boolean zigzag) {
|
||||
this.ball = ball;
|
||||
this.room = room;
|
||||
this.direction = Direction8.fromDelta(
|
||||
ball.getX() - kicker.getX(),
|
||||
ball.getY() - kicker.getY()
|
||||
);
|
||||
this.momentum = hasPath ? 55 : 0;
|
||||
this.direction = direction;
|
||||
this.momentum = momentum;
|
||||
this.isDribble = (momentum == 0);
|
||||
this.zigzag = zigzag && !this.isDribble;
|
||||
|
||||
if (this.zigzag) {
|
||||
this.zigzagA = direction.rotateDirection45Degrees(false);
|
||||
this.zigzagB = direction.rotateDirection45Degrees(true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDribble() {
|
||||
return this.isDribble;
|
||||
}
|
||||
|
||||
private boolean isTileBlocked(int x, int y) {
|
||||
RoomTile tile = this.room.getLayout().getTile((short) x, (short) y);
|
||||
if (tile == null) return true;
|
||||
if (tile.hasUnits()) return true;
|
||||
return tile.getState() != RoomTileState.OPEN;
|
||||
if (tile.getState() != RoomTileState.OPEN) return true;
|
||||
return x == this.room.getLayout().getDoorX() && y == this.room.getLayout().getDoorY();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,36 +61,109 @@ public class RebugKickBallAction implements Runnable {
|
||||
if (this.dead || !this.room.isLoaded()) return;
|
||||
|
||||
try {
|
||||
int nextX = this.ball.getX() + this.direction.getDiffX();
|
||||
int nextY = this.ball.getY() + this.direction.getDiffY();
|
||||
int nextX;
|
||||
int nextY;
|
||||
Direction8 moveDir;
|
||||
|
||||
if (isTileBlocked(nextX, nextY)) {
|
||||
this.direction = this.direction.rotateDirection180Degrees();
|
||||
if (this.zigzag) {
|
||||
Direction8 preferred = this.zigzagSide ? this.zigzagB : this.zigzagA;
|
||||
Direction8 fallback = this.zigzagSide ? this.zigzagA : this.zigzagB;
|
||||
|
||||
nextX = this.ball.getX() + preferred.getDiffX();
|
||||
nextY = this.ball.getY() + preferred.getDiffY();
|
||||
|
||||
if (isTileBlocked(nextX, nextY)) {
|
||||
nextX = this.ball.getX() + fallback.getDiffX();
|
||||
nextY = this.ball.getY() + fallback.getDiffY();
|
||||
|
||||
if (isTileBlocked(nextX, nextY)) {
|
||||
nextX = this.ball.getX() + this.direction.getDiffX();
|
||||
nextY = this.ball.getY() + this.direction.getDiffY();
|
||||
|
||||
if (isTileBlocked(nextX, nextY)) {
|
||||
this.stopBall();
|
||||
return;
|
||||
}
|
||||
moveDir = this.direction;
|
||||
} else {
|
||||
moveDir = fallback;
|
||||
}
|
||||
} else {
|
||||
moveDir = preferred;
|
||||
this.zigzagSide = !this.zigzagSide;
|
||||
}
|
||||
} else {
|
||||
nextX = this.ball.getX() + this.direction.getDiffX();
|
||||
nextY = this.ball.getY() + this.direction.getDiffY();
|
||||
|
||||
if (isTileBlocked(nextX, nextY)) {
|
||||
int dx = this.direction.getDiffX();
|
||||
int dy = this.direction.getDiffY();
|
||||
|
||||
if (dx != 0 && dy != 0) {
|
||||
boolean xBlocked = isTileBlocked(this.ball.getX() + dx, this.ball.getY());
|
||||
boolean yBlocked = isTileBlocked(this.ball.getX(), this.ball.getY() + dy);
|
||||
|
||||
if (xBlocked && !yBlocked) {
|
||||
this.direction = Direction8.fromDelta(-dx, dy);
|
||||
} else if (!xBlocked && yBlocked) {
|
||||
this.direction = Direction8.fromDelta(dx, -dy);
|
||||
} else {
|
||||
this.direction = this.direction.rotateDirection180Degrees();
|
||||
}
|
||||
} else {
|
||||
this.direction = this.direction.rotateDirection180Degrees();
|
||||
}
|
||||
|
||||
this.tilesSinceBounce = 0;
|
||||
nextX = this.ball.getX() + this.direction.getDiffX();
|
||||
nextY = this.ball.getY() + this.direction.getDiffY();
|
||||
}
|
||||
moveDir = this.direction;
|
||||
}
|
||||
|
||||
RoomTile nextTile = this.room.getLayout().getTile((short) nextX, (short) nextY);
|
||||
if (nextTile == null) return;
|
||||
if (nextTile == null) {
|
||||
this.stopBall();
|
||||
return;
|
||||
}
|
||||
|
||||
RoomTile oldTile = this.room.getLayout().getTile(this.ball.getX(), this.ball.getY());
|
||||
double oldZ = this.ball.getZ();
|
||||
|
||||
this.ball.setRotation(this.direction.getRot());
|
||||
this.ball.setRotation(moveDir.getRot());
|
||||
this.ball.setX(nextTile.x);
|
||||
this.ball.setY(nextTile.y);
|
||||
this.ball.setZ(nextTile.getStackHeight());
|
||||
this.ball.needsUpdate(true);
|
||||
|
||||
// Schedule next movement based on momentum
|
||||
long delay = getDelayForMomentum(this.momentum);
|
||||
if (delay > 0) {
|
||||
Emulator.getThreading().run(this, delay);
|
||||
if (!this.zigzag && this.tilesSinceBounce >= 0) {
|
||||
this.tilesSinceBounce++;
|
||||
}
|
||||
|
||||
// Update tiles
|
||||
this.room.updateTile(oldTile);
|
||||
this.room.updateTile(nextTile);
|
||||
if (!this.zigzag && this.tilesSinceBounce > 1 && !this.isDribble) {
|
||||
THashSet<Habbo> habbos = this.room.getHabbosAt(nextTile.x, nextTile.y);
|
||||
if (!habbos.isEmpty()) {
|
||||
this.direction = this.direction.rotateDirection180Degrees();
|
||||
this.tilesSinceBounce = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.ball.setExtradata(this.isDribble ? "2" : "5");
|
||||
this.room.sendComposer(new ItemStateComposer(this.ball).compose());
|
||||
|
||||
this.momentum -= 11;
|
||||
|
||||
if (!this.isDribble) {
|
||||
long delay = getDelayForMomentum(this.momentum);
|
||||
if (delay > 0) {
|
||||
Emulator.getThreading().run(this, delay);
|
||||
} else {
|
||||
this.stopBall();
|
||||
}
|
||||
} else {
|
||||
this.dead = true;
|
||||
}
|
||||
|
||||
THashSet<HabboItem> oldItems = this.room.getItemsAt(oldTile);
|
||||
if (oldItems != null && !oldItems.isEmpty()) {
|
||||
@@ -81,18 +171,23 @@ public class RebugKickBallAction implements Runnable {
|
||||
}
|
||||
this.room.getItemsAt(nextTile).add(this.ball);
|
||||
|
||||
// Send rolling animation
|
||||
this.room.updateTile(oldTile);
|
||||
this.room.updateTile(nextTile);
|
||||
|
||||
this.room.sendComposer(new FloorItemOnRollerComposer(
|
||||
this.ball, null, oldTile, oldZ, nextTile, this.ball.getZ(), 0.0D, this.room
|
||||
).compose());
|
||||
|
||||
// Decay momentum
|
||||
this.momentum -= 11;
|
||||
} catch (Exception e) {
|
||||
this.dead = true;
|
||||
this.stopBall();
|
||||
}
|
||||
}
|
||||
|
||||
private void stopBall() {
|
||||
this.dead = true;
|
||||
this.ball.setExtradata("0");
|
||||
this.room.sendComposer(new ItemStateComposer(this.ball).compose());
|
||||
}
|
||||
|
||||
private long getDelayForMomentum(int momentum) {
|
||||
switch (momentum) {
|
||||
case 55: return 100L;
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user