Skip to content

Instantly share code, notes, and snippets.

@darrensapalo
Created December 21, 2018 16:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darrensapalo/f0c5dec2b988f7c4d9be8d24d91b6e90 to your computer and use it in GitHub Desktop.
Save darrensapalo/f0c5dec2b988f7c4d9be8d24d91b6e90 to your computer and use it in GitHub Desktop.
Updated Stolao's daily reward script to work for Hercules server
//===== EinherjarRO Scripts ==================================
//= Requested
//===== By: ==================================================
//= Stolao
//===== Current Version: =====================================
//= 2.1D
//===== Compatible With: =====================================
//= Hercules
//===== Description: =========================================
//= A reward system for players who play more frequently
//===== Todo =================================================
//= Make Logging out then it continue count
//===== Additional Comments: =================================
//= 2.00 Origional Make
//= 2.01 Fixed Logic Bug
//= 2.02 Fixed Year Multiplier
//= 2.03 Added an IP check
//= 2.04 Split Rewards up
//= 2.05 Move ip check to Daily reward collection
//= 2.06 Added ability to see next day via commands/relogging
//= 2.07 Added Delay
//= 2.08 Moved Delay to none VIP only
//= 2.09 Removed some useless lines
//= 2.0A Removed Menus due to bugs
//= 2.0B Fixed VIP Cutin
//= 2.0C Added it showing previos day cutin
//= 2.0D Added a for loop
//= 2.0E Fixed Ramined logged in time calculation (visual bug)
//= 2.0F Added a F_InsertPlural to "more minute"
//= 2.10 Added a Mac Check
//= 2.11 Changed the IP check to [Sader1992] version
//= 2.12 Enable Bound Type
//= 2.13 Added 'collectreward', 'dailyreward', 'collectdaily' Commands
//= 2.14 Added 'nextreward' Command
//= 2.15 Added Daily Buffs
//= 2.16 Added Exp Rewards
//= 2.17 Removed Reards for Autotraders
//= 2.18 Move Buffs to Daily so VIP wont trigger twice
//= 2.19 Optimized Slightly
//= 2.1A Fixed a bug with VIP getting bonus rewards in normal rewards
//= 2.1B Fixed some documentaion
//= 2.1C Fixed extra '['
//= 2.1D-branch - Modified by fourxhackd to work for hercules (Dec 2018)
//===== Contact Info: ========================================
//= [Stolao]
//= Email: Taingram11@gmail.com
//= [fouxhackd]
//= Github: darrensapalo
//============================================================
- script LOGIN FAKE_NPC,{
// ======================================================================
// Returns the number of days the user has already logged in daily, and
// claimed their daily reward.
// ======================================================================
function daily_reward_spree {
return #DLoginSpree;
}
// ======================================================================
// Returns the number of days the user has already logged in daily, and
// claimed their daily reward.
// ======================================================================
function daily_vip_reward_spree {
return #VIPLoginSpree;
}
function is_account_vip {
return false;
}
function is_next_vip_rewards_set {
.@next_index = daily_vip_reward_spree() + 1;
return .VIPRewards$[.@next_index] != "";
}
function still_has_vip_rewards {
return daily_vip_reward_spree() < .@VIPRewardCount;
}
function still_has_rewards {
return getarg(0) < #LastVIPRewardDate;
}
function dbg {
if (false)
dispbottom getarg(0);
}
function receive_vip_rewards {
set .@today,getarg(0);
if (is_account_vip() == false) {
message strcharinfo(0),"Become a VIP to double your Daily Rewards.";
return;
}
if (still_has_vip_rewards() == false){
message strcharinfo(0),"[Daily Rewards]: No more VIP rewards remaining this month.";
return;
}
if (still_has_rewards(.@today) == false){
message strcharinfo(0),"[Daily Rewards]: VIP Rewards already collected today";
return;
}
if(checkvending() & 2 && .Mode & 256){
message strcharinfo(0),"[Daily Rewards]: Venders cannot receive rewards.";
return;
}
set .@prize_index,daily_vip_reward_spree() + 1;
explode(.@XT$,.VIPRewards$[.@prize_index],",");
.@Size = getarraysize(.@XT$);
deletearray .@TT[0],getarraysize(.@TT);
deletearray .@itms[0],getarraysize(.@itms);
deletearray .@qnts[0],getarraysize(.@qnts);
// Item rewards
for(.@x = y = 0; .@x < .@Size; .@x++)
.@TT[.@x] = atoi(.@XT$[.@x]);
if(.Mode & 1 && (.@TT[4] > 0 || .@vip[4] > 0)){
.@Size = getarraysize(.@TT);
for(.@x = 4; .@x <= .@Size - 1 ; .@x += 2){
.@itms[.@y] = .@TT[.@x];
.@qnts[.@y] = .@TT[.@x + 1];
.@y++;
}
if (checkweight2(.@itms, .@qnts)){
for(.@x = 0; .@x < .@y; .@x++){
if(.Mode & 128) {
getitembound .@itms[.@x], .@qnts[.@x], .Bound_Mode;
}
else {
getitem .@itms[.@x], .@qnts[.@x];
}
}
} else {
message strcharinfo(0),"[Daily Rewards]: You cannot carry the prizes, please use storage and relog.";
return;
}
}
// Loyalty points rewards
if(.Mode & 2 && (.@TT[1])){
#Loyalty += .@TT[1];
message strcharinfo(0),"[Daily Rewards]: Recieved "+ .@TT[1] +" "+.Points$;
}
// Zeny rewards
if(.Mode & 4 && (.@TT[0])){
Zeny += .@TT[0];
message strcharinfo(0),"[Daily Rewards]: Recieved "+ .@TT[0] +"z";
}
// Experience rewards
if(.Mode & 8 && (.@TT[3] || .@TT[4]))
getexp .@TT[3], .@TT[4];
if(.Mode & 32)
cutin .VIPCutins$[#VIPLoginSpree],4;
#VIPLoginSpree++;
#LastVIPRewardDate = .@today;
sleep2 1000;
if(.Mode & 64)
cutin .VIPCutins$[#VIPLoginSpree],4;
message strcharinfo(0),"[Daily Rewards]: You have collected your VIP reward for " + #VIPLoginSpree + " day/s this month.";
if(.Mode & 32 || .Mode & 64){
sleep2 5000;
cutin "",255;
}
}
function receive_daily_rewards {
set .@today,getarg(0);
set .@RewardCount,getarg(1);
dbg("No more rewards?");
if(#DLoginSpree > .@RewardCount){
dbg("#DLoginSpree" + #DLoginSpree + " ... .@RewardCount = " + .@RewardCount);
message strcharinfo(0),"[Daily Rewards]: No more rewards remaining this month.";
return;
}
dbg("already collected?");
if(.@today <= #LastDailyRewardDate){
message strcharinfo(0),"[Daily Rewards]: Rewards already collected today.";
return;
}
dbg("IP check");
if(.IPCheck){
dbg("Performing IP checks");
query_sql("SELECT account_id FROM `login` WHERE last_ip = '"+getcharip()+"'", .@AccountId);
.@Size = getarraysize(.@AccountId);
for(.@i=0; .@i < .@Size; .@i++){
query_sql("SELECT `value` FROM `acc_reg_num_db` WHERE `account_id` = '"+.@AccountId[.@i]+"' AND `key` = '#LastDailyRewardDate'",.@LastIp2);
dbg(".@i=" + .@i + " .@LastIp2=" + .@LastIp2);
if(.@i < .@LastIp2){
message strcharinfo(0),"You did not receive the Daily Rewards because it is limited to 1 account per IP.";
return;
}
}
}
if(.Rest){
.@time = gettime(7) * 60 * 24 * 365 + gettime(8) * 60 * 24 + gettime(3) * 60 + gettime(2);
if(.@time < @logintime + .Rest){
.@delay = @logintime + .Rest - .@time;
message strcharinfo(0),"[Daily Rewards]: to collect your daily reward, you must remain logged in for " + .@delay + " more minute/s.";
return;
}
}
dbg("Setting next day");
deletearray .@XT$[0],getarraysize(.@XT$);
.@NextDay = #DLoginSpree + 1;
explode(.@XT$,.Rewards$[.@NextDay],",");
.@Size = getarraysize(.@XT$);
deletearray .@TT[0],getarraysize(.@TT);
deletearray .@itms[0],getarraysize(.@itms);
deletearray .@qnts[0],getarraysize(.@qnts);
// Item rewards
for(.@x = y = 0; .@x < .@Size; .@x++)
.@TT[.@x] = atoi(.@XT$[.@x]);
if(.Mode & 1 && (.@TT[4] > 0 || .@vip[4] > 0)){
.@Size = getarraysize(.@TT);
for(.@x = 4; .@x <= .@Size - 1 ; .@x += 2){
.@itms[.@y] = .@TT[.@x];
.@qnts[.@y] = .@TT[.@x + 1];
.@y++;
}
if (checkweight2(.@itms, .@qnts)){
for(.@x = 0; .@x < .@y; .@x++){
if(.Mode & 128) {
getitembound .@itms[.@x], .@qnts[.@x], .Bound_Mode;
}
else {
getitem .@itms[.@x], .@qnts[.@x];
}
}
} else {
message strcharinfo(0),"[Daily Rewards]: You cannot carry the prizes, please use storage and relog.";
return;
}
}
// Loyalty points rewards
if(.Mode & 2 && (.@TT[1])){
#Loyalty += .@TT[1];
message strcharinfo(0),"[Daily Rewards]: Recieved "+ .@TT[1] +" "+.Points$;
}
// Zeny rewards
if(.Mode & 4 && (.@TT[0])){
Zeny += .@TT[0];
message strcharinfo(0),"[Daily Rewards]: Recieved "+ .@TT[0] +"z.";
}
// Experience rewards
if(.Mode & 8 && (.@TT[3] || .@TT[4]))
getexp .@TT[3], .@TT[4];
// Buff rewards
if(.Mode & 16){
.@Size = getarraysize(.BuffInfo);
dbg("Buffing...");
dbg("Next day: " + .@NextDay);
for(.@x = 0; .@x < .@Size; .@x += 4){
.@sc_id = .BuffInfo[.@x];
.@current_day = .BuffInfo[.@x + 1];
.@duration_in_min = .BuffInfo[.@x + 2];
.@val1 = .BuffInfo[.@x + 3];
if(.@NextDay == .@current_day) {
dbg(" Data: " + .@sc_id + " , " + .@duration_in_min + " , " + .@val1);
sc_start .@sc_id, .@duration_in_min * 60000, .@val1;
}
}
}
if(.Mode & 32)
cutin .Cutins$[#DLoginSpree],4;
#DLoginSpree++;
#LastDailyRewardDate = .@today;
sleep2 1000;
if(.Mode & 64)
cutin .Cutins$[#DLoginSpree],4;
message strcharinfo(0),"[Daily Rewards]: You have collected your daily reward for " + #DLoginSpree + " day/s this month.";
return;
}
OnPCLoginEvent:
if(!@logintime)
@logintime = gettime(7) * 60 * 24 * 365 + gettime(8) * 60 * 24 + gettime(3) * 60 + gettime(2);
OnLoginCmnd:
.@today = gettime(7) * 12 * 31 + gettime(6) * 31 + gettime(5);
if(.ResetsOnSpreeEnd && .@today > #LastDailyRewardDate + 1)
#DLoginSpree = 0;
if(.ResetsOnSpreeEnd && .@today > #LastVIPRewardDate + 1)
#VIPLoginSpree = 0;
.@VIPRewardCount = getarraysize(.VIPRewards$);
.@RewardCount = getarraysize(.Rewards$);
if(#DLoginSpree >= .@RewardCount && #VIPLoginSpree >= .@VIPRewardCount){
#LastDailyRewardDate = .@today;
#LastVIPRewardDate = .@today;
end;
}
dbg("oof");
sleep2 1000;
dbg("ok");
if ( is_account_vip() && is_next_vip_rewards_set() ) {
dbg("vip");
receive_vip_rewards(.@today, .@VIPRewardCount);
}
dbg("daily");
receive_daily_rewards(.@today, .@RewardCount);
// Delay, and then close
sleep2(5000);
cutin("",255);
end;
OnNextCmnd:
.@today = gettime(7) * 60 * 24 * 365 + gettime(8) * 60 * 24 + gettime(3) * 60 + gettime(2);
.@i = gettime(7) * 12 * 31 + gettime(6) * 31 + gettime(5);
if(.@i <= #LastDailyRewardDate)
message strcharinfo(0),"[Daily Rewards]: Your next reward will be available tomorrow.";
else {
.@days = (.@today >= @logintime + .Rest) / 60 / 24;
.@hours = ((.@today >= @logintime + .Rest) / 60) % 24;
.@mins = (.@today >= @logintime + .Rest) % 60;
message strcharinfo(0),"[Daily Rewards]: You have "+ ((.@days) ? .@days +" Days " : "") + ((.@hours) ? .@hours +" Hours " : "") + ((.@mins) ? .@mins +" Minutes " : "") +"till your next reward.";
}
if(.Mode & 32 || .Mode & 64){
if(.@NextDay >= getarraysize(.Rewards$))
.@g = 0;
else .@g = #DLoginSpree + 1;
cutin .Cutins$[.@g],4;
}
end;
OnHour00:
if(gettime(5) == 1){
query_sql("DELETE FROM `acc_reg_num_db` WHERE `key` = '#DLoginSpree' OR `key` = '#VIPLoginSpree' OR `key` = '#LastVIPRewardDate' OR `key` = '#LastDailyRewardDate'");
attachrid(0);
#DLoginSpree = #VIPLoginSpree = #LastVIPRewardDate = #LastDailyRewardDate = 0;
}
end;
OnWhisperGlobal:
if (getgmlevel() == 99) {
mes "[Daily Rewards]";
mes "Do you want to reset all daily reward sprees and reward dates?";
if (select("No", "Yes, reset") == 1) {
close;
}
close2;
goto OnHour00;
end;
}
OnInit:
// Basic Settings
// 1: Item | 2: Points | 4: Zeny | 8: Exp
// 16: Gain Buffs
// 32: Show Cutins | 64: Show Next Day Cutin
// 128: Item Rewards Bound
// 256: No Rewards for Autotraders
// (a bit value, e.g. 3 = Items & Points from Multi)
.Mode = 1|2|4|16|128|256; // |32 64|
// Item Binding Mode
// 1 : Bound_Account : Account Bound item
// 2 : Bound_Guild : Guild Bound item
// 3 : Bound_Party : Party Bound item
// 4 : Bound_Char : Character Bound item
.Bound_Mode = 1;
// To disable the command '@loginreward' comment the next lines
// * Needs extra commands for typos
//bindatcmd("relog","LOGIN::OnLoginCmnd",0,99);
//bindatcmd("collectreward",strnpcinfo(3)+"::OnLoginCmnd",0,99);
bindatcmd("dailyreward",strnpcinfo(3)+"::OnLoginCmnd",0,99);
//bindatcmd("collectdaily",strnpcinfo(3)+"::OnLoginCmnd",0,99);
bindatcmd("nextreward",strnpcinfo(3)+"::OnNextCmnd",0,99);
// Reset days back to 0 on a skipped day.
// Toggle
// [0] = Off
// [1] = On
.ResetsOnSpreeEnd = 1;
// .Rest
// Delay after login to collect rewards
// In Minutes
.Rest = 0;
// Point Name
.Points$ = "Loyalty Points";
// Ip check to prevent multi accounts
// Toggle
// [0] = Off
// [1] = On
.IPCheck = 0;
// Consecutive Days Buff
// Each buff contains 4 variables
// <Type>,<Days>,<Duration>,<Rate>, // Buff 1
// <Type>,<Days>,<Duration>,<Rate>, // Buff 2
// ...;
//
// Example: 188,7,45,3
// -On the 7th day logged in Player gains +3 Str for 45 mins
//
// Type is 188, which references which SC_ to use, SC_INCSTR in this example
// -For a full list of SC_ visit the db/const.txt
// Days is days buff is applied, in this example 7, so every 7th day, 14,21,28....
// Duration is buff duration is Minuits, in this example 45 mins
// Rate is buff val1, in this example player gains 3 Str
setarray .BuffInfo
,SC_BLESSING,1,30,3 // Bless Lv. 3 for 30 Mins on 1st Day
,SC_CASH_DEATHPENALTY,2,120,1 // Life Insurance for 120 Mins on 2nd Day
,SC_BLESSING,3,120,5 // Bless Lv. 5 for 120 Mins on 3rd Day
,SC_CASH_DEATHPENALTY,4,120,1 // Life Insurance for 120 Min on 4th Day
,SC_INC_AGI,5,120,10 // Inc Agi Lv. 10 for 120 Mins on 5th Day
,SC_BLESSING,6,120,10 // Bless Lv. 10 for 2 hours on 6th Day
,SC_CASH_PLUSEXP,7,240,50 // +50% Exp for 4 hours on 7th Day
,SC_CASH_DEATHPENALTY,8,240,1 // Life Insurance for 4 Hours on 8th Day
,SC_INC_AGI,9,120,10 // Bless Lv. 10 for 120 Mins on 9th Day
,SC_BLESSING,10,120,25 // Inc Agi Lv. 10 for 120 Mins on 10th Day
,SC_INC_AGI,12,120,10 // Bless Lv. 10 for 120 Mins on 12th Day
,SC_CASH_PLUSEXP,14,240,50 // +50% Exp for 240 Mins on 14th Day
,SC_BLESSING,15,120,25 // Inc Agi Lv. 10 for 120 Mins on 15th Day
,SC_CASH_DEATHPENALTY,16,360,1 // Life Insurance for 360 on 16th Day
,SC_INC_AGI,18,120,10 // Bless Lv. 10 for 120 Mins on 18th Day
,SC_BLESSING,20,120,25 // Inc Agi Lv. 10 for 120 Mins on 20th Day
,SC_CASH_PLUSEXP,21,240,50 // +50% Exp for 240 Mins on 21st Day
,SC_CASH_DEATHPENALTY,22,360,1 // Life Insurance for 360 on 22nd Day
,SC_INC_AGI,24,120,10 // Bless Lv. 10 for 120 Mins on 24th Day
,SC_BLESSING,25,120,25 // Inc Agi Lv. 10 for 120 Mins on 25th Day
,SC_CASH_DEATHPENALTY,26,360,1 // Life Insurance for 360 on 26th Day
,SC_INC_AGI,27,120,10 // Bless Lv. 10 for 120 Mins on 27th Day
,SC_CASH_PLUSEXP,28,240,50 // +50% Exp for 240 Mins on 28th Day
,SC_BLESSING,30,120,25; // Inc Agi Lv. 10 for 120 Mins on 30th Day
// Daily Prize items:
// "<Zeny>,<Points>,<BaseExp>,<JobExp>,<itemID-1>,<amount-1>,<itemID-2>,<amount-2>...etc", // Day 1
// "<Zeny>,<Points>,<BaseExp>,<JobExp>,<itemID-1>,<amount-1>,<itemID-2>,<amount-2>...etc" // Day 2
// ...;
// Total length of any days string must be 255 or shorter
setarray .Rewards$[1],
"1000", // Day 1: 1000 Zeny
"2500,0,0,0,501,50,602,5", // Day 2: 2500z, 50 Red Potion, 5 Butterfly Wings
"0,0,0,0,505,15,601,50", // Day 3: 15 Blue Potion, 50 Fly Wings
"1000", // Day 4: 1000 Zeny
"2500", // Day 5: 2500 Zeny
"2500,0,0,0,11705,10", // Day 6: 2500 Zeny, 10 Child's White Potion
"0,0,0,0,12208,1", // Day 7: 1 Battle Manual
"2500", // Day 8: 2500 Zeny
"0,0,0,0,11705,15", // Day 6: 15 Child's White Potion
"5000", // Day 9: 2500 Zeny
"0,0,0,0,505,50", // Day 10: 50 Blue Potion
"2500", // Day 11: 2500 Zeny
"0,0,0,0,11705,25", // Day 6: 25 Child's White Potion
"2500", // Day 13: 2500 Zeny
"0,0,0,0,11705,100,12210,1",// Day 14: 100 Child's White Potion + 1 Bubble Gum
"5000", // Day 15: 2500 Zeny
"0,0,0,0,11705,25", // Day 6: 25 Child's White Potion
"5000", // Day 17: 5000 Zeny
"0,0,0,0,616,2", // Day 18: 2 Old Card Albums
"3000", // Day 19: 3000 Zeny
"3000", // Day 20: 3000 Zeny
"0,0,0,0,12210,1", // Day 21: 1 Bubble Gum
"1000", // Day 22: 1000 Zeny
"0,0,0,0,12208,1,12210,1", // Day 23: 1 Battle Manual, 1 Bubble Gum
"2500", // Day 24: 2500 Zeny
"2500", // Day 25: 2500 Zeny
"0,0,0,0,503,5,506,3", // Day 26: 5 White Potion + 3 Green Potion
"2500", // Day 27: 2500 Zeny
"15000,0,0,0,13695,1"; // Day 28: 15000 Zeny, 1 Box of 25 Field Manuals (25%)
// VIP Prize items:
// "<Zeny>,<Points>,<BaseExp>,<JobExp>,<itemID-1>,<amount-1>,<itemID-2>,<amount-2>...etc", // Day 1
// "<Zeny>,<Points>,<BaseExp>,<JobExp>,<itemID-1>,<amount-1>,<itemID-2>,<amount-2>...etc" // Day 2
// ...;
// Total length of any days string must be 255 or shorter
//
// Note VIPs Collect both VIP and Normal Player rewards
setarray .VIPRewards$[1],
"1000", // Day 1: 1000 Zeny
"0,0,0,0,501,5", // Day 2: 5 Red Potion
"0,0,0,0,506,5", // Day 3: 5 Green Potion
"2000", // Day 4: 2000 Zeny
"2000", // Day 5: 2000 Zeny
"0,0,0,0,502,5", // Day 6: 5 Orange Potion
"0,0,0,0,12208,1", // Day 7: 1 Battle Manual
"2500", // Day 8: 2500 Zeny
"2500", // Day 8: 2500 Zeny
"2500", // Day 9: 2500 Zeny
"0,0,0,0,503,5", // Day 10: 5 White Potion
"2500", // Day 11: 2500 Zeny
"2500", // Day 12: 2500 Zeny
"2500", // Day 13: 2500 Zeny
"0,0,0,0,503,5,506,3", // Day 14: 5 White Potion + 3 Green Potion
"2500", // Day 15: 2500 Zeny
"2500", // Day 16: 2500 Zeny
"2500", // Day 17: 2500 Zeny
"0,0,0,0,503,5,506,3", // Day 18: 5 White Potion + 3 Green Potion
"2500", // Day 19: 2500 Zeny
"2500", // Day 20: 2500 Zeny
"0,0,0,0,604,3", // Day 21: 1 Dead Branch
"2500", // Day 22: 2500 Zeny
"0,0,0,0,503,5,506,3", // Day 23: 5 White Potion + 3 Green Potion
"2500", // Day 24: 2500 Zeny
"2500", // Day 25: 2500 Zeny
"0,0,0,0,503,5,506,3", // Day 26: 5 White Potion + 3 Green Potion
"2500", // Day 27: 2500 Zeny
"2500"; // Day 28: 2500 Zeny
// Cutin Array
// Shows a cutin before collecting for each date
// Start at 0 if showing next day
setarray .Cutins$,
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_08",
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_08",
"kafra_09",
"kafra_01",
"kafra_02",
"kafra_03";
// Cutin Array
// Shows a cutin on collecting for each date
setarray .VIPCutins$,
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_08",
"kafra_01",
"kafra_02",
"kafra_03",
"kafra_04",
"kafra_05",
"kafra_06",
"kafra_07",
"kafra_08",
"kafra_09",
"kafra_01",
"kafra_02",
"kafra_03";
end;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment