mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-08-01 15:31:16 +00:00
Merge develop and add functionality to party chat
This commit is contained in:
commit
c1c2f025ad
17 changed files with 152 additions and 50 deletions
10
migrations/csvexport.phy
Normal file
10
migrations/csvexport.phy
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import csv
|
||||
|
||||
data = csv.reader(open('/home/slappybag/backrs/800dollar.csv', 'rb'), delimiter=",", quotechar='|')
|
||||
column = []
|
||||
|
||||
for row in data:
|
||||
column.append(row[9])
|
||||
|
||||
print "one:"
|
||||
print column
|
||||
|
|
@ -48,8 +48,15 @@ module.exports.hpModifier = (value, armorDef, helmDef, shieldDef, level, priorit
|
|||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
module.exports.gpModifier = (value, modifier, priority='!') ->
|
||||
return value * modifier * priorityValue(priority)
|
||||
module.exports.gpModifier = (value, modifier, priority='!', streak, model) ->
|
||||
val = value * modifier * priorityValue(priority)
|
||||
if streak and model
|
||||
streakBonus = streak / 100 + 1 # eg, 1-day streak is 1.1, 2-day is 1.2, etc
|
||||
afterStreak = val * streakBonus
|
||||
model.set('_streakBonus', afterStreak - val) if (val > 0) # can we do this without model? just global emit?
|
||||
return afterStreak
|
||||
else
|
||||
return val
|
||||
|
||||
###
|
||||
Calculates the next task.value based on direction
|
||||
|
|
|
|||
|
|
@ -157,18 +157,32 @@ setupGrowlNotifications = (model) ->
|
|||
else if num > 0
|
||||
statsNotification "<i class='icon-star'></i> + #{rounded} XP", 'xp'
|
||||
|
||||
user.on 'set', 'stats.gp', (captures, args) ->
|
||||
num = captures - args
|
||||
absolute = Math.abs(num)
|
||||
###
|
||||
Show "+ 5 {gold_coin} 3 {silver_coin}"
|
||||
###
|
||||
showCoins = (money) ->
|
||||
absolute = Math.abs(money)
|
||||
gold = Math.floor(absolute)
|
||||
silver = Math.floor((absolute-gold)*100)
|
||||
sign = if num < 0 then '-' else '+'
|
||||
if gold and silver > 0
|
||||
statsNotification "#{sign} #{gold} <i class='icon-gold'></i> #{silver} <i class='icon-silver'></i>", 'gp'
|
||||
return "#{gold} <i class='icon-gold'></i> #{silver} <i class='icon-silver'></i>"
|
||||
else if gold > 0
|
||||
statsNotification "#{sign} #{gold} <i class='icon-gold'></i>", 'gp'
|
||||
return "#{gold} <i class='icon-gold'></i>"
|
||||
else if silver > 0
|
||||
statsNotification "#{sign} #{silver} <i class='icon-silver'></i>", 'gp'
|
||||
return "#{silver} <i class='icon-silver'></i>"
|
||||
|
||||
user.on 'set', 'stats.gp', (captures, args) ->
|
||||
money = captures - args
|
||||
return unless !!money # why is this happening? gotta find where stats.gp is being set from (-)habit
|
||||
sign = if money < 0 then '-' else '+'
|
||||
statsNotification "#{sign} #{showCoins(money)}", 'gp'
|
||||
|
||||
# Append Bonus
|
||||
bonus = model.get('_streakBonus')
|
||||
if (money > 0) and !!bonus
|
||||
bonus = 0.01 if bonus < 0.01
|
||||
statsNotification "+ #{showCoins(bonus)} Streak Bonus!"
|
||||
model.del('_streakBonus')
|
||||
|
||||
user.on 'set', 'stats.lvl', (captures, args) ->
|
||||
if captures > args
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ removeWhitespace = (str) ->
|
|||
|
||||
username = (auth, override) ->
|
||||
#some people define custom profile name in Avatar -> Profile
|
||||
return override if override?
|
||||
return override if override
|
||||
|
||||
if auth?.facebook?.displayName?
|
||||
auth.facebook.displayName
|
||||
|
|
|
|||
|
|
@ -166,12 +166,18 @@ module.exports.app = (appExports, model, app) ->
|
|||
return unless /\S/.test text
|
||||
chat.unshift
|
||||
id: model.id()
|
||||
uuid: user.get('id')
|
||||
contributor: user.get('backer.contributor')
|
||||
npc: user.get('backer.npc')
|
||||
text: text
|
||||
user: helpers.username(model.get('_user.auth'), model.get('_user.profile.name'))
|
||||
timestamp: +new Date
|
||||
model.set(input, '')
|
||||
chat.remove 200 # keep a max messages cap
|
||||
|
||||
model.on 'unshift', '_party.chat', -> $('.chat-message').tooltip()
|
||||
model.on 'unshift', '_tavern.chat.messages', -> $('.chat-message').tooltip()
|
||||
|
||||
appExports.partySendChat = ->
|
||||
sendChat('_party.chat', '_chatMessage')
|
||||
model.set '_user.party.lastMessageSeen', model.get('_party.chat')[0].id
|
||||
|
|
@ -180,6 +186,10 @@ module.exports.app = (appExports, model, app) ->
|
|||
model.setNull '_tavern.chat', {messages:[]} #we can remove this later, first time run only
|
||||
sendChat('_tavern.chat.messages', '_tavernMessage')
|
||||
|
||||
appExports.partyMessageKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.partySendChat()
|
||||
|
||||
appExports.tavernMessageKeyup = (e, el, next) ->
|
||||
return next() unless e.keyCode is 13
|
||||
appExports.tavernSendChat()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ MODIFIER = algos.MODIFIER # each new level, armor, weapon add 2% modifier (this
|
|||
###
|
||||
Drop System
|
||||
###
|
||||
randomDrop = (model, delta, priority) ->
|
||||
randomDrop = (model, delta, priority, streak=0) ->
|
||||
user = model.at('_user')
|
||||
|
||||
# limit drops to 2 / day
|
||||
|
|
@ -23,11 +23,10 @@ randomDrop = (model, delta, priority) ->
|
|||
return if reachedDropLimit and model.flags.nodeEnv != 'development'
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
# debugging purpose - 50% chance during development, 3% chance on prod
|
||||
chanceMultiplier = if (model.flags.nodeEnv is 'development') then 50 else 1
|
||||
# TODO temporary min cap of 1 so people still get rewarded for good habits. Will change once we have streaks
|
||||
deltaMultiplier = if Math.abs(delta) < 1 then 1 else Math.abs(delta)
|
||||
chanceMultiplier = chanceMultiplier * deltaMultiplier * algos.priorityValue(priority) # multiply chance by reddness
|
||||
chanceMultiplier = Math.abs(delta)
|
||||
chanceMultiplier *= algos.priorityValue(priority) # multiply chance by reddness
|
||||
chanceMultiplier += streak # streak bonus
|
||||
console.log chanceMultiplier
|
||||
|
||||
if user.get('flags.dropsEnabled') and Math.random() < (.05 * chanceMultiplier)
|
||||
# current breakdown - 3% (adjustable) chance on drop
|
||||
|
|
@ -94,7 +93,7 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
|
||||
taskPath = "tasks.#{taskId}"
|
||||
taskObj = obj.tasks[taskId]
|
||||
{type, value} = taskObj
|
||||
{type, value, streak} = taskObj
|
||||
priority = taskObj.priority or '!'
|
||||
|
||||
# If they're trying to purhcase a too-expensive reward, confirm they want to take a hit for it
|
||||
|
|
@ -120,7 +119,10 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
level = user.get('stats.lvl')
|
||||
weaponStrength = items.items.weapon[user.get('items.weapon')].strength
|
||||
exp += algos.expModifier(delta,weaponStrength,level, priority) / 2 # / 2 hack for now bcause people leveling too fast
|
||||
gp += algos.gpModifier(delta, 1, priority)
|
||||
if streak
|
||||
gp += algos.gpModifier(delta, 1, priority, streak, model)
|
||||
else
|
||||
gp += algos.gpModifier(delta, 1, priority)
|
||||
|
||||
subtractPoints = ->
|
||||
level = user.get('stats.lvl')
|
||||
|
|
@ -144,10 +146,18 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
if cron? # cron
|
||||
calculateDelta()
|
||||
subtractPoints()
|
||||
batch.set "#{taskPath}.streak", 0
|
||||
else
|
||||
calculateDelta(false)
|
||||
if delta != 0
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
if direction is 'up'
|
||||
streak = if streak then streak + 1 else 1
|
||||
else
|
||||
streak = if streak then streak - 1 else 0
|
||||
batch.set "#{taskPath}.streak", streak
|
||||
taskObj.streak = streak
|
||||
|
||||
|
||||
when 'todo'
|
||||
if cron? #cron
|
||||
|
|
@ -183,7 +193,7 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
batch.commit()
|
||||
|
||||
# Drop system
|
||||
randomDrop(model, delta, priority) if direction is 'up'
|
||||
randomDrop(model, delta, priority, streak) if direction is 'up'
|
||||
|
||||
return delta
|
||||
|
||||
|
|
|
|||
|
|
@ -68,20 +68,28 @@ module.exports.app = (appExports, model) ->
|
|||
user.on 'push', 'items.pets', (after, before) ->
|
||||
return if user.get('achievements.beastMaster')
|
||||
if before >= 90 # evidently before is the count?
|
||||
dontPersist = model._dontPersist
|
||||
model._dontPersist = false
|
||||
user.set 'achievements.beastMaster', true, ->
|
||||
model._dontPersist = dontPersist
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.beastMaster', true, (-> model._dontPersist = dontPersist)
|
||||
$('#beastmaster-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'items.*', (after, before) ->
|
||||
return if user.get('achievements.ultimateGear')
|
||||
items = user.get('items')
|
||||
if parseInt(items.weapon) == 6 and parseInt(items.armor) == 5 and parseInt(items.head) == 5 and parseInt(items.shield) == 5
|
||||
debugger
|
||||
dontPersist = model._dontPersist
|
||||
model._dontPersist = false
|
||||
user.set 'achievements.ultimateGear', true, ->
|
||||
model._dontPersist = dontPersist
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.set 'achievements.ultimateGear', true, (-> model._dontPersist = dontPersist)
|
||||
$('#max-gear-achievement-modal').modal('show')
|
||||
|
||||
user.on 'set', 'tasks.*.streak', (id, after, before) ->
|
||||
if after > 0
|
||||
|
||||
# 21-day streak, as per the old philosophy of doign a thing 21-days in a row makes a habit
|
||||
if (after % 21) is 0
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.incr 'achievements.streak', 1, (-> model._dontPersist = dontPersist)
|
||||
$('#streak-achievement-modal').modal('show')
|
||||
|
||||
# they're undoing a task at the 21 mark, take back their badge
|
||||
else if (before - after is 1) and (before % 21 is 0)
|
||||
dontPersist = model._dontPersist; model._dontPersist = false
|
||||
user.incr 'achievements.streak', -1, (-> model._dontPersist = dontPersist)
|
||||
|
|
|
|||
|
|
@ -39,8 +39,11 @@ userAccess = (store) ->
|
|||
uid = captures.shift()
|
||||
attrPath = captures.join('.') # new array shifted left, after shift() was run
|
||||
|
||||
if attrPath is 'backer'
|
||||
return accept(false) # we can only manually set this stuff in the database
|
||||
|
||||
# public access to users.*.party.invitation (TODO, lock down a bit more)
|
||||
if (attrPath == 'party.invitation')
|
||||
if attrPath is 'party.invitation'
|
||||
return accept(true)
|
||||
|
||||
# Same session (user.id = this.session.userId)
|
||||
|
|
@ -91,6 +94,7 @@ partySystem = (store) ->
|
|||
'party',
|
||||
'profile',
|
||||
'achievements',
|
||||
'backer',
|
||||
'preferences',
|
||||
'auth.local.username',
|
||||
'auth.facebook.displayName')
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
position relative
|
||||
height 300px
|
||||
|
||||
.tavern-chat
|
||||
.tavern-chat, .party-chat
|
||||
|
||||
li
|
||||
height 60px
|
||||
|
|
|
|||
|
|
@ -13,10 +13,16 @@
|
|||
<hr/>
|
||||
|
||||
<p>
|
||||
<h4>5/4/2013</h4>
|
||||
<ul>
|
||||
<li><a _target='blank' href="https://trello.com/card/streaks-consecutive-bonus/50e5d3684fe3a7266b0036d6/182">Streaks</a>. You get a GP & drop-% increase the longer you hold daily streaks (they stack). You also get a stacking badge for each 21-day streak.</li>
|
||||
</ul>
|
||||
|
||||
<h4>5/3/2013</h4>
|
||||
<ul>
|
||||
<li>Two new achievements: Beast Master & Ultimate Gear. Got ideas for more achievements? <a target="_blank" href="https://trello.com/card/awards-badges/50e5d3684fe3a7266b0036d6/19">chime in here</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>5/2/2013</h4>
|
||||
<ul>
|
||||
<li><a target="_blank" href="https://trello.com/card/party-chat/50e5d3684fe3a7266b0036d6/267">Party Chat!</a> also, Tavern Chat (LFG)</li>
|
||||
|
|
@ -24,6 +30,7 @@
|
|||
<li>NPCs! <a target="_blank" href="http://www.kickstarter.com/profile/mihakuu">Bailey</a> the Town Crier, <a target="_blank" href="http://www.kickstarter.com/profile/523661924">Alexander</a> the <a target="_blank" href="https://trello.com/card/marketplace/50e5d3684fe3a7266b0036d6/167">Merchant</a>, <a target="_blank" href="http://www.kickstarter.com/profile/2014640723">Daniel</a> the <a target="_blank" href="http://goo.gl/FkSib">Tavern Keep</a></li>
|
||||
<li><a href="https://github.com/lefnire/habitrpg/issues/828">New "Game Options" layout</a> (click your avatar to see)</li>
|
||||
</ul>
|
||||
|
||||
<h4>3/27/2013</h4>
|
||||
<ul>
|
||||
<li>Drop system + pets overhaul (<a href="http://www.kickstarter.com/projects/lefnire/habitrpg-mobile/posts/439433">Blog Post</a> | <a href="https://trello.com/card/pets/50e5d3684fe3a7266b0036d6/166">Trello Card</a>)</li>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
data-checkpet="{#if @profile.items.pet}hasPet{/}"
|
||||
data-checkuser="{{#if @main}}isUser{{/}}"
|
||||
data-uid="{{@profile.id}}" x-bind="click:clickAvatar"
|
||||
rel="popover" data-placement="bottom" data-trigger="hover" data-html="true" data-content="{{#unless @main}}
|
||||
rel="popover" data-placement="bottom" data-trigger="hover" data-html="true" data-content="
|
||||
<div>
|
||||
<div class='progress progress-danger' style='height:5px;'>
|
||||
<div class='bar' style='height: 5px; width: {percent(@profile.stats.hp, 50)}%;'></div>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<div>GP: {floor(@profile.stats.gp)}</div>
|
||||
<div>{count(@profile.items.pets)} / 90 Pets Found</div>
|
||||
</div>
|
||||
{{/}}">
|
||||
">
|
||||
<div class='character-sprites'>
|
||||
{#with @profile.preferences as :p}
|
||||
<span class='{:p.gender}_skin_{:p.skin}'></span>
|
||||
|
|
@ -168,7 +168,6 @@
|
|||
<!--<div class='achievement achievement-sword'></div>-->
|
||||
<!--<div class='achievement achievement-tree'></div>-->
|
||||
<!--<div class='achievement achievement-comment'></div>-->
|
||||
<!--<div class='achievement achievement-armor'></div>-->
|
||||
<!--<div class='achievement achievement-cave'></div>-->
|
||||
<!--<div class='achievement achievement-sun'></div>-->
|
||||
<!--<div class='achievement achievement-boot'></div>-->
|
||||
|
|
@ -177,11 +176,24 @@
|
|||
<!--<div class='achievement achievement-ninja'></div>-->
|
||||
<!--<div class='achievement achievement-cactus'></div>-->
|
||||
<!--<div class='achievement achievement-bow'></div>-->
|
||||
<!--<div class='achievement achievement-heart'></div>-->
|
||||
<!--<div class='achievement achievement-shield'></div>-->
|
||||
<!--<div class='achievement achievement-thermometer'></div>-->
|
||||
<!--<div class='achievement achievement-firefox'></div>-->
|
||||
<!--<div class='achievement achievement-karaoke'></div>-->
|
||||
{{#if @profile.backer.npc}}
|
||||
<div class='achievement achievement-helm'></div><span class='label label-success'>{{@profile.backer.npc}} NPC</span>
|
||||
<hr/>
|
||||
{{/}}
|
||||
{{#if @profile.backer.contributor}}
|
||||
<div class='achievement achievement-firefox'></div><span class='label label-inverse'>{{@profile.backer.contributor}}</span>
|
||||
<hr/>
|
||||
{{/}}
|
||||
{{#if @profile.backer.tier}}
|
||||
<div class='achievement achievement-heart'></div>Kickstarter Backer - ${{@profile.backer.tier}} Tier
|
||||
<hr/>
|
||||
{{/}}
|
||||
{#if @profile.achievements.streak}
|
||||
<div class='achievement achievement-thermometer'></div> {@profile.achievements.streak} Streak Achievement(s)
|
||||
<hr/>
|
||||
{/}
|
||||
{#if @profile.achievements.originalUser}
|
||||
<div class='achievement achievement-cake'></div>Original User
|
||||
<hr/>
|
||||
|
|
@ -191,7 +203,7 @@
|
|||
<hr/>
|
||||
{/}
|
||||
{#if @profile.achievements.ultimateGear}
|
||||
<div class='achievement achievement-helm'></div>Ultimate Gear
|
||||
<div class='achievement achievement-armor'></div>Ultimate Gear
|
||||
<hr/>
|
||||
{/}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,9 +91,7 @@
|
|||
</form>
|
||||
<ul class='unstyled tavern-chat'>
|
||||
{#each _tavern.chat.messages as :message}
|
||||
<li class="{#if indexOf(:message.text, _user.profile.name)}highlight{/if}">
|
||||
<span class='badge'>{:message.user}</span> {:message.text} - <span class='muted'>{relativeDate(:message.timestamp)}</span>
|
||||
</li>
|
||||
<app:party:chat-message message={{:message}} />
|
||||
{/}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<app:pets:modals />
|
||||
<app:alerts:modals />
|
||||
<app:rewards:modals />
|
||||
<app:tasks:modals />
|
||||
|
||||
<!-- Game Over Modal -->
|
||||
<div style="{#unless equal(_user.stats.lvl,0)}display:none;{/}">
|
||||
|
|
|
|||
|
|
@ -22,15 +22,12 @@
|
|||
<div class="span6">
|
||||
<h3>Chat</h3>
|
||||
<form x-bind='submit:partySendChat'>
|
||||
<textarea>{_chatMessage}</textarea><br/>
|
||||
<textarea x-bind='keyup:partyMessageKeyup'>{_chatMessage}</textarea><br/>
|
||||
<input class=btn type=submit value=Submit />
|
||||
</form>
|
||||
<ul class='unstyled'>
|
||||
<ul class='party-chat unstyled'>
|
||||
{#each _party.chat as :message}
|
||||
<li>
|
||||
<hr/>
|
||||
<span class='badge'>{:message.user}</span> {:message.text} - <span class='muted'>{friendlyTimestamp(:message.timestamp)}</span>
|
||||
</li>
|
||||
<app:party:chat-message message={{:message}} />
|
||||
{/}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -58,3 +55,13 @@
|
|||
</form>
|
||||
|
||||
{/}
|
||||
|
||||
|
||||
<chat-message:>
|
||||
<li class="{{#if indexOf(:message.text, _user.profile.name)}}highlight{{/if}}">
|
||||
<span
|
||||
class="label {{#if @message.npc}}label-success{{else if @message.contributor}}label-inverse{{else if equal(@message.uuid,_user.id)}}label-info{{/}} chat-message"
|
||||
rel='tooltip' title="{{@message.contributor}}{{@message.npc}}">
|
||||
{{@message.user}}</span> {{@message.text}} - <span class='muted'>{{relativeDate(@message.timestamp)}}
|
||||
</span>
|
||||
</li>
|
||||
|
|
@ -124,9 +124,9 @@
|
|||
|
||||
<stable-pet:>
|
||||
<div rel="tooltip" title="{{@tooltip}}">
|
||||
{{#if ownsPet(@pet, _user.items.pets)}}
|
||||
{#if ownsPet(@pet, _user.items.pets)}
|
||||
<button class="pet-button Pet-{{@pet}} {#if equal(_user.items.currentPet.name, @string)}active{/if}" data-pet="{{@pet}}" x-bind="click:choosePet"></button>
|
||||
{{else}}
|
||||
{else}
|
||||
<button class="pet-button pet-not-owned" data-pet="{{@pet}}"><img src="/img/paw.png"/></button>
|
||||
{{/}}
|
||||
{/}
|
||||
</div>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<modals:>
|
||||
<app:modals:modal modalId='max-gear-achievement-modal' header="Achievement Unlocked!">
|
||||
<p>
|
||||
<div class='achievement achievement-helm'></div>You have earned the "Ultimate Gear" Achievement for upgrading to the maximum gear set!
|
||||
<div class='achievement achievement-armor'></div>You have earned the "Ultimate Gear" Achievement for upgrading to the maximum gear set!
|
||||
</p>
|
||||
<@footer>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
<modals:>
|
||||
<app:modals:modal modalId='streak-achievement-modal' header="Achievement!">
|
||||
<p>
|
||||
<div class='achievement achievement-thermometer'></div>You have stacked your "Streaker" Achievement! Every 21 days of streak, you gain 1 achievement point here.
|
||||
</p>
|
||||
<@footer>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
</@footer>
|
||||
</app:modals:modal>
|
||||
|
||||
<ads: nonvoid>
|
||||
{{#if and( _loggedIn, notEqual(_user.flags.ads,'hide') )}}
|
||||
<span class='pull-right'>
|
||||
|
|
@ -98,6 +108,10 @@
|
|||
|
||||
<!-- right-hand side control buttons -->
|
||||
<div class="task-meta-controls">
|
||||
<!-- Streak -->
|
||||
<span class='{#unless :task.streak}hide{/}'>
|
||||
{:task.streak}<span rel='tooltip' title='Streak Counter'><i class='icon-forward'></i></span>
|
||||
</span>
|
||||
<!-- edit -->
|
||||
<a x-bind=click:toggleTaskEdit data-hide-id="{{:task.id}}-chart" data-toggle-id="{{:task.id}}-edit" rel=tooltip title="Edit"><i class="icon-pencil"></i></a>
|
||||
<!-- delete -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue