mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
fix(wired-ui): clarify reward fields and mute alerts
This commit is contained in:
@@ -7,6 +7,131 @@ import { NitroInput } from '../../../../layout';
|
|||||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||||
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
import { WiredSourcesSelector } from '../WiredSourcesSelector';
|
||||||
|
|
||||||
|
type RewardType = 'badge' | 'credits' | 'pixels' | 'diamonds' | 'points' | 'furni' | 'respect';
|
||||||
|
|
||||||
|
interface RewardEntry
|
||||||
|
{
|
||||||
|
rewardType: RewardType;
|
||||||
|
rewardValue: string;
|
||||||
|
probability: number;
|
||||||
|
pointsType: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_PROBABILITY = 100;
|
||||||
|
const DEFAULT_POINTS_TYPE = 5;
|
||||||
|
|
||||||
|
const REWARD_TYPES: { value: RewardType, label: string }[] = [
|
||||||
|
{ value: 'badge', label: 'Badge' },
|
||||||
|
{ value: 'credits', label: 'Credits' },
|
||||||
|
{ value: 'pixels', label: 'Pixels / Duckets' },
|
||||||
|
{ value: 'diamonds', label: 'Diamonds' },
|
||||||
|
{ value: 'points', label: 'Extra Currency' },
|
||||||
|
{ value: 'furni', label: 'Furni' },
|
||||||
|
{ value: 'respect', label: 'Respect' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const SELECTABLE_REWARD_TYPES = REWARD_TYPES.filter(entry => (entry.value !== 'respect'));
|
||||||
|
|
||||||
|
const createReward = (): RewardEntry =>
|
||||||
|
({
|
||||||
|
rewardType: 'furni',
|
||||||
|
rewardValue: '',
|
||||||
|
probability: DEFAULT_PROBABILITY,
|
||||||
|
pointsType: DEFAULT_POINTS_TYPE
|
||||||
|
});
|
||||||
|
|
||||||
|
const getRewardValuePlaceholder = (rewardType: RewardType) =>
|
||||||
|
{
|
||||||
|
switch(rewardType)
|
||||||
|
{
|
||||||
|
case 'badge':
|
||||||
|
return 'Badge code';
|
||||||
|
case 'credits':
|
||||||
|
return 'Credits amount';
|
||||||
|
case 'pixels':
|
||||||
|
return 'Pixels amount';
|
||||||
|
case 'diamonds':
|
||||||
|
return 'Diamonds amount';
|
||||||
|
case 'points':
|
||||||
|
return 'Amount';
|
||||||
|
case 'furni':
|
||||||
|
return 'Furni base item id';
|
||||||
|
case 'respect':
|
||||||
|
return 'Respect amount';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExtraFieldLabel = (rewardType: RewardType) =>
|
||||||
|
{
|
||||||
|
switch(rewardType)
|
||||||
|
{
|
||||||
|
case 'points':
|
||||||
|
return 'Currency Type';
|
||||||
|
case 'badge':
|
||||||
|
return 'Code';
|
||||||
|
default:
|
||||||
|
return 'Info';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExtraFieldPlaceholder = (rewardType: RewardType) =>
|
||||||
|
{
|
||||||
|
switch(rewardType)
|
||||||
|
{
|
||||||
|
case 'points':
|
||||||
|
return 'Type id (e.g. 105)';
|
||||||
|
case 'badge':
|
||||||
|
return 'Badge';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseRewardEntry = (rawType: string, rawCode: string, rawProbability: string): RewardEntry =>
|
||||||
|
{
|
||||||
|
const probability = Number(rawProbability);
|
||||||
|
const parsedProbability = Number.isFinite(probability) ? probability : DEFAULT_PROBABILITY;
|
||||||
|
|
||||||
|
if(rawType === '0')
|
||||||
|
{
|
||||||
|
return { rewardType: 'badge', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
|
||||||
|
}
|
||||||
|
|
||||||
|
const separatorIndex = rawCode.indexOf('#');
|
||||||
|
|
||||||
|
if(separatorIndex === -1)
|
||||||
|
{
|
||||||
|
return { rewardType: 'furni', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rewardType = rawCode.slice(0, separatorIndex);
|
||||||
|
const rewardValue = rawCode.slice(separatorIndex + 1);
|
||||||
|
|
||||||
|
if(rewardType.startsWith('points'))
|
||||||
|
{
|
||||||
|
const pointsType = Number(rewardType.slice('points'.length));
|
||||||
|
|
||||||
|
return {
|
||||||
|
rewardType: 'points',
|
||||||
|
rewardValue,
|
||||||
|
probability: parsedProbability,
|
||||||
|
pointsType: Number.isFinite(pointsType) ? pointsType : DEFAULT_POINTS_TYPE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(REWARD_TYPES.some(entry => (entry.value === rewardType)))
|
||||||
|
{
|
||||||
|
return { rewardType: rewardType as RewardType, rewardValue, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rewardType === 'cata')
|
||||||
|
{
|
||||||
|
return { rewardType: 'furni', rewardValue, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { rewardType: 'furni', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
|
||||||
|
};
|
||||||
|
|
||||||
export const WiredActionGiveRewardView: FC<{}> = props =>
|
export const WiredActionGiveRewardView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
const [ limitEnabled, setLimitEnabled ] = useState(false);
|
const [ limitEnabled, setLimitEnabled ] = useState(false);
|
||||||
@@ -14,7 +139,7 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
const [ uniqueRewards, setUniqueRewards ] = useState(false);
|
const [ uniqueRewards, setUniqueRewards ] = useState(false);
|
||||||
const [ rewardsLimit, setRewardsLimit ] = useState(1);
|
const [ rewardsLimit, setRewardsLimit ] = useState(1);
|
||||||
const [ limitationInterval, setLimitationInterval ] = useState(1);
|
const [ limitationInterval, setLimitationInterval ] = useState(1);
|
||||||
const [ rewards, setRewards ] = useState<{ isBadge: boolean, itemCode: string, probability: number }[]>([]);
|
const [ rewards, setRewards ] = useState<RewardEntry[]>([]);
|
||||||
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
|
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
|
||||||
const [ userSource, setUserSource ] = useState<number>(() =>
|
const [ userSource, setUserSource ] = useState<number>(() =>
|
||||||
{
|
{
|
||||||
@@ -22,7 +147,8 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const addReward = () => setRewards(rewards => [ ...rewards, { isBadge: false, itemCode: '', probability: null } ]);
|
const addReward = () => setRewards(rewards => [ ...rewards, createReward() ]);
|
||||||
|
const hasCustomCurrencyReward = rewards.some(reward => (reward.rewardType === 'points'));
|
||||||
|
|
||||||
const removeReward = (index: number) =>
|
const removeReward = (index: number) =>
|
||||||
{
|
{
|
||||||
@@ -36,18 +162,9 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateReward = (index: number, isBadge: boolean, itemCode: string, probability: number) =>
|
const updateReward = (index: number, updater: (reward: RewardEntry) => RewardEntry) =>
|
||||||
{
|
{
|
||||||
const rewardsClone = Array.from(rewards);
|
setRewards(prevValue => prevValue.map((reward, rewardIndex) => ((rewardIndex === index) ? updater(reward) : reward)));
|
||||||
const reward = rewardsClone[index];
|
|
||||||
|
|
||||||
if(!reward) return;
|
|
||||||
|
|
||||||
reward.isBadge = isBadge;
|
|
||||||
reward.itemCode = itemCode;
|
|
||||||
reward.probability = probability;
|
|
||||||
|
|
||||||
setRewards(rewardsClone);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = () =>
|
const save = () =>
|
||||||
@@ -56,9 +173,20 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
|
|
||||||
for(const reward of rewards)
|
for(const reward of rewards)
|
||||||
{
|
{
|
||||||
if(!reward.itemCode) continue;
|
const rewardValue = reward.rewardValue.trim();
|
||||||
|
|
||||||
const rewardsString = [ reward.isBadge ? '0' : '1', reward.itemCode, reward.probability.toString() ];
|
if(!rewardValue) continue;
|
||||||
|
|
||||||
|
const probability = Math.max(0, Number.isFinite(reward.probability) ? reward.probability : DEFAULT_PROBABILITY);
|
||||||
|
const rewardCode = (() =>
|
||||||
|
{
|
||||||
|
if(reward.rewardType === 'badge') return rewardValue;
|
||||||
|
if(reward.rewardType === 'points') return `points${ Math.max(0, reward.pointsType) }#${ rewardValue }`;
|
||||||
|
|
||||||
|
return `${ reward.rewardType }#${ rewardValue }`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const rewardsString = [ reward.rewardType === 'badge' ? '0' : '1', rewardCode, (uniqueRewards ? DEFAULT_PROBABILITY : probability).toString() ];
|
||||||
stringRewards.push(rewardsString.join(','));
|
stringRewards.push(rewardsString.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +199,9 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
const readRewards: { isBadge: boolean, itemCode: string, probability: number }[] = [];
|
const readRewards: RewardEntry[] = [];
|
||||||
|
|
||||||
if(trigger.stringData.length > 0 && trigger.stringData.includes(';'))
|
if(trigger.stringData.length > 0)
|
||||||
{
|
{
|
||||||
const splittedRewards = trigger.stringData.split(';');
|
const splittedRewards = trigger.stringData.split(';');
|
||||||
|
|
||||||
@@ -83,11 +211,11 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
|
|
||||||
if(reward.length !== 3) continue;
|
if(reward.length !== 3) continue;
|
||||||
|
|
||||||
readRewards.push({ isBadge: reward[0] === '0', itemCode: reward[1], probability: Number(reward[2]) });
|
readRewards.push(parseRewardEntry(reward[0], reward[1], reward[2]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(readRewards.length === 0) readRewards.push({ isBadge: false, itemCode: '', probability: null });
|
if(readRewards.length === 0) readRewards.push(createReward());
|
||||||
|
|
||||||
setRewardTime((trigger.intData.length > 0) ? trigger.intData[0] : 0);
|
setRewardTime((trigger.intData.length > 0) ? trigger.intData[0] : 0);
|
||||||
setUniqueRewards((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false);
|
setUniqueRewards((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false);
|
||||||
@@ -147,24 +275,64 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="grid grid-cols-[1.2fr_1fr_110px_150px_42px] gap-1 px-1">
|
||||||
|
<Text small bold>Type</Text>
|
||||||
|
<Text small bold>Amount / Value</Text>
|
||||||
|
<Text small bold>{ uniqueRewards ? 'Mode' : 'Chance %' }</Text>
|
||||||
|
<Text small bold>{ hasCustomCurrencyReward ? 'Currency Type' : 'Extra / Info' }</Text>
|
||||||
|
<Text small bold>Action</Text>
|
||||||
|
</div>
|
||||||
{ rewards && rewards.map((reward, index) =>
|
{ rewards && rewards.map((reward, index) =>
|
||||||
{
|
{
|
||||||
|
const rewardTypeOptions = (reward.rewardType === 'respect')
|
||||||
|
? REWARD_TYPES
|
||||||
|
: SELECTABLE_REWARD_TYPES;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={ index } className="flex gap-1">
|
<div key={ index } className="grid grid-cols-[1.2fr_1fr_110px_150px_42px] gap-1">
|
||||||
<div className="flex items-center gap-1">
|
<select className="w-full form-select form-select-sm" value={ reward.rewardType } onChange={ event => updateReward(index, prevValue => ({ ...prevValue, rewardType: event.target.value as RewardType, rewardValue: '' })) }>
|
||||||
<input checked={ reward.isBadge } className="form-check-input" type="checkbox" onChange={ (e) => updateReward(index, e.target.checked, reward.itemCode, reward.probability) } />
|
{ rewardTypeOptions.map(entry => <option key={ entry.value } value={ entry.value }>{ entry.label }</option>) }
|
||||||
<Text small>Badge?</Text>
|
</select>
|
||||||
|
<NitroInput
|
||||||
|
placeholder={ getRewardValuePlaceholder(reward.rewardType) }
|
||||||
|
type={ reward.rewardType === 'badge' ? 'text' : 'number' }
|
||||||
|
value={ reward.rewardValue }
|
||||||
|
onChange={ event => updateReward(index, prevValue => ({ ...prevValue, rewardValue: event.target.value })) } />
|
||||||
|
{ uniqueRewards
|
||||||
|
? <div className="flex items-center px-2 rounded bg-muted">
|
||||||
|
<Text small>Unique</Text>
|
||||||
|
</div>
|
||||||
|
: <NitroInput
|
||||||
|
min={ 0 }
|
||||||
|
max={ 100 }
|
||||||
|
placeholder="Chance %"
|
||||||
|
type="number"
|
||||||
|
value={ reward.probability }
|
||||||
|
onChange={ event => updateReward(index, prevValue => ({ ...prevValue, probability: Number(event.target.value) })) } /> }
|
||||||
|
{ (reward.rewardType === 'points')
|
||||||
|
?
|
||||||
|
<NitroInput
|
||||||
|
min={ 0 }
|
||||||
|
placeholder={ getExtraFieldPlaceholder(reward.rewardType) }
|
||||||
|
type="number"
|
||||||
|
value={ reward.pointsType }
|
||||||
|
onChange={ event => updateReward(index, prevValue => ({ ...prevValue, pointsType: Number(event.target.value) })) } />
|
||||||
|
: <div className="flex items-center px-2 rounded bg-muted">
|
||||||
|
<Text small>{ getExtraFieldLabel(reward.rewardType) }</Text>
|
||||||
|
</div> }
|
||||||
|
<div className="flex items-center justify-end">
|
||||||
|
{ (index > 0) &&
|
||||||
|
<Button variant="danger" onClick={ event => removeReward(index) }>
|
||||||
|
<FaTrash className="fa-icon" />
|
||||||
|
</Button> }
|
||||||
</div>
|
</div>
|
||||||
<NitroInput placeholder="Item Code" type="text" value={ reward.itemCode } onChange={ e => updateReward(index, reward.isBadge, e.target.value, reward.probability) } />
|
|
||||||
<NitroInput placeholder="Probability" type="number" value={ reward.probability } onChange={ e => updateReward(index, reward.isBadge, reward.itemCode, Number(e.target.value)) } />
|
|
||||||
{ (index > 0) &&
|
|
||||||
<Button variant="danger" onClick={ event => removeReward(index) }>
|
|
||||||
<FaTrash className="fa-icon" />
|
|
||||||
</Button> }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
<Text center small className="p-1 rounded bg-muted">
|
||||||
|
Extra Currency uses Amount as the quantity and Currency Type as the purse type id. Example: amount 200 + type 105.
|
||||||
|
</Text>
|
||||||
</WiredActionBaseView>
|
</WiredActionBaseView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d } from '@nitrots/nitro-renderer';
|
import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d, WiredRewardResultMessageEvent } from '@nitrots/nitro-renderer';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useBetween } from 'use-between';
|
import { useBetween } from 'use-between';
|
||||||
import { GetConfigurationValue, LocalizeBadgeName, LocalizeText, NotificationAlertItem, NotificationAlertType, NotificationBubbleItem, NotificationBubbleType, NotificationConfirmItem, PlaySound, ProductImageUtility, TradingNotificationType } from '../../api';
|
import { GetConfigurationValue, LocalizeBadgeName, LocalizeText, NotificationAlertItem, NotificationAlertType, NotificationBubbleItem, NotificationBubbleType, NotificationConfirmItem, PlaySound, ProductImageUtility, TradingNotificationType } from '../../api';
|
||||||
@@ -397,6 +397,28 @@ const useNotificationState = () =>
|
|||||||
simpleAlert(LocalizeText(parser.alertMessage), NotificationAlertType.DEFAULT, null, null, LocalizeText(parser.titleMessage ? parser.titleMessage : 'notifications.broadcast.title'));
|
simpleAlert(LocalizeText(parser.alertMessage), NotificationAlertType.DEFAULT, null, null, LocalizeText(parser.titleMessage ? parser.titleMessage : 'notifications.broadcast.title'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useMessageEvent<WiredRewardResultMessageEvent>(WiredRewardResultMessageEvent, event =>
|
||||||
|
{
|
||||||
|
const parser = event.getParser();
|
||||||
|
|
||||||
|
switch(parser.reason)
|
||||||
|
{
|
||||||
|
case WiredRewardResultMessageEvent.PRODUCT_DONATED_CODE:
|
||||||
|
case WiredRewardResultMessageEvent.BADGE_DONATED_CODE:
|
||||||
|
simpleAlert(LocalizeText('wiredfurni.rewardsuccess.body'), NotificationAlertType.DEFAULT, null, null, LocalizeText('wiredfurni.rewardsuccess.title'));
|
||||||
|
return;
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 8:
|
||||||
|
simpleAlert(LocalizeText(`wiredfurni.rewardfailed.reason.${ parser.reason }`), NotificationAlertType.DEFAULT, null, null, LocalizeText('wiredfurni.rewardfailed.title'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const onRoomEnterEvent = useCallback(() =>
|
const onRoomEnterEvent = useCallback(() =>
|
||||||
{
|
{
|
||||||
if(modDisclaimerShown) return;
|
if(modDisclaimerShown) return;
|
||||||
|
|||||||
@@ -123,9 +123,10 @@ const useChatWidgetState = () =>
|
|||||||
text = LocalizeText('widget.chatbubble.handitem', ['username', 'handitem'], [username, LocalizeText(('handitem' + event.extraParam))]);
|
text = LocalizeText('widget.chatbubble.handitem', ['username', 'handitem'], [username, LocalizeText(('handitem' + event.extraParam))]);
|
||||||
break;
|
break;
|
||||||
case RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING: {
|
case RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING: {
|
||||||
const hours = ((event.extraParam > 0) ? Math.floor((event.extraParam / 3600)) : 0).toString();
|
const remainingSeconds = Math.max(0, event.extraParam);
|
||||||
const minutes = ((event.extraParam > 0) ? Math.floor((event.extraParam % 3600) / 60) : 0).toString();
|
const hours = Math.floor(remainingSeconds / 3600).toString();
|
||||||
const seconds = (event.extraParam % 60).toString();
|
const minutes = Math.floor((remainingSeconds % 3600) / 60).toString();
|
||||||
|
const seconds = (remainingSeconds % 60).toString();
|
||||||
|
|
||||||
text = LocalizeText('widget.chatbubble.mutetime', ['hours', 'minutes', 'seconds'], [hours, minutes, seconds]);
|
text = LocalizeText('widget.chatbubble.mutetime', ['hours', 'minutes', 'seconds'], [hours, minutes, seconds]);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user