1. Análise da Arquitetura

Na arquitetura nativa e pura do Arcturus Morningstar 5.1.1, a categoria Wired Seletor (Selector) não existe no ecossistema. O motor original (WiredHandler) processa as pilhas estritamente na ordem: Triggers -> Conditions -> Effects. Seletores são uma mecânica mais recente do Habbo oficial.

Como vamos contornar isso com maestria? Para não forçar você a reescrever o core do WiredHandler.java inteiro do seu emulador, a comunidade adapta "Seletores de Habbos" como Condições (InteractionWiredCondition). Ele vai avaliar o usuário que disparou o trigger e cortar a execução do ciclo se ele não pertencer ao grupo mapeado. Se o seu emulador for um fork modificado que já possui a classe base InteractionWiredSelector, você só precisa trocar o extends no arquivo Java que criei abaixo.

Abaixo está a solução Full Stack para criar esse Wired.

2. Estrutura de Arquivos

  • [SQL] wired_seletor_grupo.sql

  • [Backend] WiredConditionType.java (Modificação)

  • [Backend] ItemManager.java (Modificação)

  • [Backend] WiredConditionSelectorHabbosGroup.java (Novo)

  • [Frontend] WiredConditionLayoutCode.ts (Modificação)

  • [Frontend] WiredConditionSelectorHabbosGroupView.tsx (Novo)

3. Banco de Dados (SQL)

Execute no MySQL. Ajuste o page_id para a aba correta do seu catálogo onde ficam as Condições (ou Seletores, se você os separou no catálogo).

SQL
INSERT INTO `items_base` (`id`, `sprite_id`, `public_name`, `item_name`, `type`, `width`, `length`, `stack_height`, `allow_stack`, `allow_sit`, `allow_lay`, `allow_walk`, `allow_gift`, `allow_trade`, `allow_recycle`, `allow_marketplace_sell`, `allow_inventory_stack`, `interaction_type`, `interaction_modes_count`, `vending_ids`, `multiheight`, `customparams`, `effect_id_male`, `effect_id_female`, `clothing_on_walk`) 
VALUES (NULL, 8883, 'Wired Seletor: Habbos no grupo', 'wf_c_selector_habbos_group', 's', 1, 1, 0.5, 1, 0, 0, 0, 1, 1, 0, 0, 1, 'wf_c_selector_habbos_group', 2, '', '', '', 0, 0, '');

INSERT INTO `catalog_items` (`id`, `page_id`, `item_ids`, `catalog_name`, `cost_credits`, `cost_points`, `points_type`, `amount`, `limited_stack`, `limited_sells`, `order_num`, `offer_id`, `song_id`, `extradata`, `badge`, `offer_active`) 
VALUES (NULL, 123, (SELECT id FROM items_base WHERE item_name = 'wf_c_selector_habbos_group' LIMIT 1), 'wf_c_selector_habbos_group', 5, 0, 0, 1, 0, 0, 1, -1, 0, '', '', '1');

4. Backend Arcturus (Java)

Passo A: Adicione um ID para nosso "Seletor" no Enum de Condições. Abra com.eu.habbo.habbohotel.wired.WiredConditionType e adicione ao fim:

Java
    // ...
    SELECTOR_HABBOS_IN_GROUP(32); // Certifique-se de que o ID 32 está livre no seu emulador

Passo B: Crie a classe de Interação. Crie o arquivo WiredConditionSelectorHabbosGroup.java no package com.eu.habbo.habbohotel.items.interactions.wired.conditions:

Java
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;

import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.wired.WiredConditionType;
import com.eu.habbo.habbohotel.wired.WiredHandler;
import com.eu.habbo.messages.ServerMessage;

import java.sql.ResultSet;
import java.sql.SQLException;

public class WiredConditionSelectorHabbosGroup extends InteractionWiredCondition {
    public static final WiredConditionType type = WiredConditionType.SELECTOR_HABBOS_IN_GROUP;

    private int groupSource = 0; // 0 = Grupo do Mobi/Quarto, 1 = Grupo do User Ativador
    private int limit = 5;       // Quantidade máxima (Habbos)

    public WiredConditionSelectorHabbosGroup(ResultSet set, Item baseItem) throws SQLException {
        super(set, baseItem);
    }

    public WiredConditionSelectorHabbosGroup(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
        super(id, userId, item, extradata, limitedStack, limitedSells);
    }

    @Override
    public WiredConditionType getType() {
        return type;
    }

    @Override
    public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
        Habbo habbo = room.getHabbo(roomUnit);
        if (habbo == null) return false;

        int groupIdToCheck = 0;

        if (this.groupSource == 0) {
            // Grupo do quarto. No Arcturus, Mobis herdam o guildId do quarto a menos que utilizem custom guilds no extradata
            if (room.getGuildId() > 0) {
                groupIdToCheck = room.getGuildId();
            } else {
                return false; // Não há grupo definido na fonte (quarto)
            }
        } else if (this.groupSource == 1) {
            // Verifica o grupo primário ativo do próprio usuário que pisou/ativou
            groupIdToCheck = habbo.getHabboStats().guild;
            if (groupIdToCheck == 0) return false;
        }

        // Condição real: O usuário pertence a este grupo específico?
        return habbo.getHabboStats().hasGuild(groupIdToCheck);
    }

    @Override
    public boolean saveData(WiredSettings settings) {
        if (settings.getIntParams().length < 2) return false;

        this.groupSource = settings.getIntParams()[0];
        this.limit = settings.getIntParams()[1];

        return true;
    }

    @Override
    public void serializeWiredData(ServerMessage message, Room room) {
        message.appendBoolean(false);
        message.appendInt(5); // Limite de itens na interface
        message.appendInt(0);
        message.appendInt(this.getBaseItem().getSpriteId());
        message.appendInt(this.getId());
        message.appendString("");
        message.appendInt(2); // Quantidade de intParams
        message.appendInt(this.groupSource);
        message.appendInt(this.limit);
        message.appendInt(0);
        message.appendInt(this.getType().code);
    }

    @Override
    public String getWiredData() {
        return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.groupSource, this.limit));
    }

    @Override
    public void loadWiredData(ResultSet set, Room room) throws SQLException {
        String wiredData = set.getString("wired_data");
        if (wiredData.startsWith("{")) {
            JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class);
            this.groupSource = data.groupSource;
            this.limit = data.limit;
        }
    }

    @Override
    public void onPickUp() {
        this.groupSource = 0;
        this.limit = 5;
    }

    static class JsonData {
        int groupSource, limit;
        public JsonData(int groupSource, int limit) {
            this.groupSource = groupSource;
            this.limit = limit;
        }
    }
}

Passo C: Registre a classe em ItemManager.java. Adicione no método de mapeamento de interações (loadInteractions):

Java
this.registerInteraction("wf_c_selector_habbos_group", WiredConditionSelectorHabbosGroup.class);

5. Frontend Nitro React (TypeScript)

Mapearemos nossa UI na pasta de Conditions usando o LayoutCode correspondente.

Passo A: Abra src/api/wired/WiredConditionLayoutCode.ts e adicione a referência.

TypeScript
export class WiredConditionLayoutCode {
    // ...
    public static SELECTOR_HABBOS_IN_GROUP: number = 32; // Igual ao Java
}

Passo B: Crie o componente UI igual ao da imagem. Salve em src/components/wired/views/conditions/WiredConditionSelectorHabbosGroupView.tsx:

TypeScript
import { FC, useEffect, useState } from 'react';
import { WiredConditionLayoutCode, WiredFurniType } from '../../../../api';
import { Column, Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredConditionBaseView } from './WiredConditionBaseView';

export const WiredConditionSelectorHabbosGroupView: FC<{}> = props => {
    const [groupSource, setGroupSource] = useState<number>(0);
    const [limit, setLimit] = useState<number>(5);
    const { trigger = null, setIntParams = null } = useWired();

    const save = () => setIntParams([groupSource, limit]);

    useEffect(() => {
        if (!trigger || trigger.intData.length < 2) return;

        setGroupSource(trigger.intData[0]);
        setLimit(trigger.intData[1]);
    }, [trigger]);

    return (
        <WiredConditionBaseView requiresFurni={WiredFurniType.STUFF_SELECTION_OPTION_NONE} hasSpecialInput={true} save={save}>
            <Column gap={1} className="w-100">
                
                <Column gap={1}>
                    <Text bold>Selecione a fonte do grupo</Text>
                    <select className="form-select form-select-sm" value={groupSource} onChange={e => setGroupSource(Number(e.target.value))}>
                        <option value={0}>Grupo do mobi</option>
                        <option value={1}>Grupo do usuário ativador</option>
                    </select>
                </Column>
                
                <hr className="m-0 bg-dark" />
                
                <Column gap={1}>
                    <Text bold>Limite a seleção a (Habbos)</Text>
                    <input 
                        type="number" 
                        className="form-control form-control-sm" 
                        value={limit} 
                        min={1} 
                        max={50} 
                        onChange={e => setLimit(Number(e.target.value))} 
                    />
                </Column>

            </Column>
        </WiredConditionBaseView>
    );
}

Passo C: Registre a renderização do componente. Geralmente no Switch do arquivo WiredView.tsx ou em WiredConditionBaseView.tsx (a depender do seu fork do Nitro):

TypeScript
import { WiredConditionSelectorHabbosGroupView } from './views/conditions/WiredConditionSelectorHabbosGroupView';

// ... switch (code)
case WiredConditionLayoutCode.SELECTOR_HABBOS_IN_GROUP:
    return <WiredConditionSelectorHabbosGroupView />;

Implementação

Execute seu mvn clean install para compilar o Arcturus e um yarn build para recompilar o Nitro. Recomendo utilizar um sprite provisório de Condição para esse item caso não tenha a SWF nativa dos seletores!