├── README.md ├── assets ├── 1x │ ├── Partners.png │ └── icon.png └── 2x │ ├── Partners.png │ └── icon.png ├── config.lua ├── localization ├── en-us.lua └── zh_CN.lua ├── lovely.toml ├── manifest.json └── partner.lua /README.md: -------------------------------------------------------------------------------- 1 | # Partner 2 | Partner is a vanilla content API mod that adds a new card type to the game. In short, you can accept a partner to accompany you at the beginning of each game. Initially 16 base partners with hand-crafted pixel art are provided. You can also create your own partner by browsing the tutorial or viewing the Partner code. 3 | 4 | ![logo-o](https://github.com/user-attachments/assets/ab06cfd7-918a-40fd-9ed0-17af7b5b9929) 5 | 6 | ## Requirements 7 | [Steamodded](https://github.com/Steamodded/smods) `Steamodded (>=1.0.0~BETA-0323b)` 8 | 9 | [Lovely](https://github.com/ethangreen-dev/lovely-injector) `Lovely (>=0.6)` 10 | 11 | ## Features 12 | Initially provide 16 basic Partners. 13 | 14 | If you have a Joker that corresponds to the Partner you accept, the Partner will become more powerful with a Buff form! The initial 16 basic Partners all have a Buff form. 15 | 16 | | Partner | Base form | Buff form | 17 | |----------|------------------------------------|--------------------------------------------------| 18 | | Jimbo | Gain +2 Chips | Gain +4 Chips | 19 | | Mute | Retrigger 1 additional time | Retrigger 2 additional times | 20 | | Punch | Gain +0.5 Mult | Gain +1 Mult | 21 | | Hatch | Sell value +$1 | Sell value +$2 | 22 | | Steal | Gain +2 Hands | Gain +4 Hands | 23 | | Pale | Earn $1 | Earn $2 | 24 | | Fantasy | Add an extra option of Joker card | Extra option does not take up the optional count | 25 | | Divine | Stock an extra Arcana Pack | Stock an extra free Mega Arcana Pack | 26 | | Plate | Skip blind will increase $1 | Skip blind will increase $2 | 27 | | Batter | Skip pack will increase 1 Mult | Skip pack will increase 2 Mult | 28 | | Bargain | Destroy up to 1 card | Destroy up to 5 cards | 29 | | Literacy | Give Negative edition | Give Negative edition and free | 30 | | Jump | Gain +X0.5 Mult | Gain +X1 Mult | 31 | | Vote | Retrigger 1 additional time | Retrigger 2 additional times | 32 | | Bleed | Give X1.5 Mult | Give X3 Mult | 33 | | Blaze | Upgrade 1 level | Upgrade 2 levels | 34 | 35 | The unlocking condition for all basic Partners are used the corresponding Joker to win on Gold Stake difficulty. 36 | -------------------------------------------------------------------------------- /assets/1x/Partners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icecanno/Partner-API/a2b9ba3f40037ca1cb85900404cd4f8ccb592379/assets/1x/Partners.png -------------------------------------------------------------------------------- /assets/1x/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icecanno/Partner-API/a2b9ba3f40037ca1cb85900404cd4f8ccb592379/assets/1x/icon.png -------------------------------------------------------------------------------- /assets/2x/Partners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icecanno/Partner-API/a2b9ba3f40037ca1cb85900404cd4f8ccb592379/assets/2x/Partners.png -------------------------------------------------------------------------------- /assets/2x/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Icecanno/Partner-API/a2b9ba3f40037ca1cb85900404cd4f8ccb592379/assets/2x/icon.png -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | enable_partner = true, 3 | enable_speech_bubble = true, 4 | temporary_unlock_all = false, 5 | } 6 | -------------------------------------------------------------------------------- /localization/en-us.lua: -------------------------------------------------------------------------------- 1 | return { 2 | descriptions = { 3 | Partner={ 4 | pnr_partner_jimbo={ 5 | name = "Jimbo", 6 | text = { 7 | "Gains {C:chips}+#2#{} Chips", 8 | "per hand played", 9 | "{C:inactive}(Currently {C:chips}+#1#{C:inactive} Chips)", 10 | }, 11 | unlock={ 12 | "Win a run with", 13 | "{C:attention}Joker{} on", 14 | "{C:attention}Gold Stake{} difficulty", 15 | }, 16 | }, 17 | pnr_partner_mute={ 18 | name = "Mute", 19 | text = { 20 | "Retrigger {C:attention}last card{}", 21 | "held in hand ability", 22 | }, 23 | unlock={ 24 | "Win a run with", 25 | "{C:attention}Mime{} on", 26 | "{C:attention}Gold Stake{} difficulty", 27 | }, 28 | }, 29 | pnr_partner_mute_1={ 30 | name = "Mute", 31 | text = { 32 | "Retrigger {C:attention}last card{}", 33 | "held in hand ability", 34 | "{C:attention}2{} additional times", 35 | }, 36 | unlock={ 37 | "Win a run with", 38 | "{C:attention}Mime{} on", 39 | "{C:attention}Gold Stake{} difficulty ", 40 | }, 41 | }, 42 | pnr_partner_unite={ 43 | name = "Unite", 44 | text = { 45 | "The {C:attention}highest{} ranked", 46 | "card held in hand", 47 | "gives {X:mult,C:white}X#1#{} Mult", 48 | }, 49 | unlock={ 50 | "Win a run with", 51 | "{C:attention}Raised Fist{} on", 52 | "{C:attention}Gold Stake{} difficulty", 53 | }, 54 | }, 55 | pnr_partner_hatch={ 56 | name = "Hatch", 57 | text = { 58 | "Add {C:money}$#1#{} of sell value", 59 | "to every {C:attention}Joker{}", 60 | "at end of round", 61 | }, 62 | unlock={ 63 | "Win a run with", 64 | "{C:attention}Egg{} on", 65 | "{C:attention}Gold Stake{} difficulty", 66 | }, 67 | }, 68 | pnr_partner_steal={ 69 | name = "Steal", 70 | text = { 71 | "When {C:attention}Boss Blind{}", 72 | "is selected,", 73 | "gain {C:blue}+#1#{} Hands", 74 | }, 75 | unlock={ 76 | "Win a run with", 77 | "{C:attention}Buglar{} on", 78 | "{C:attention}Gold Stake{} difficulty", 79 | }, 80 | }, 81 | pnr_partner_steal_1={ 82 | name = "Steal", 83 | text = { 84 | "When {C:attention}Blind{} is selected,", 85 | "gain {C:red}+#1#{} Discards", 86 | }, 87 | unlock={ 88 | "Win a run with", 89 | "{C:attention}Burglar{} on", 90 | "{C:attention}Gold Stake{} difficulty", 91 | }, 92 | }, 93 | pnr_partner_pale={ 94 | name = "Pale", 95 | text = { 96 | "Earn {C:money}$#3#{} for every", 97 | "{C:attention}#1# {C:inactive}[#2#]{} {C:attention}face cards", 98 | "discarded", 99 | }, 100 | unlock={ 101 | "Win a run with", 102 | "{C:attention}Faceless Joker{} on", 103 | "{C:attention}Gold Stake{} difficulty", 104 | }, 105 | }, 106 | pnr_partner_penalty={ 107 | name = "Penalty", 108 | text = { 109 | "Recover {C:attention}half{} the cost", 110 | "when any {C:attention}Booster Pack{}", 111 | "is skipped", 112 | }, 113 | unlock={ 114 | "Win a run with", 115 | "{C:attention}Red Card{} on", 116 | "{C:attention}Gold Stake{} difficulty", 117 | }, 118 | }, 119 | pnr_partner_penalty_1={ 120 | name = "Penalty", 121 | text = { 122 | "Recover the {C:attention}full{} cost", 123 | "when any {C:attention}Booster Pack{}", 124 | "is skipped", 125 | }, 126 | unlock={ 127 | "Win a run with", 128 | "{C:attention}Red Card{} on", 129 | "on {C:attention}Gold Stake{} difficulty", 130 | }, 131 | }, 132 | pnr_partner_fantasy={ 133 | name = "Fantasy", 134 | text = { 135 | "{C:green}#1# in #2#{} chance to create", 136 | "a {C:spectral}Spectral{} card when any", 137 | "{C:attention}Booster Pack{} is opened", 138 | "{C:inactive}(Must have room)", 139 | }, 140 | unlock={ 141 | "Win a run with", 142 | "{C:attention}Hallucination{} on", 143 | "{C:attention}Gold Stake{} difficulty", 144 | }, 145 | }, 146 | pnr_partner_oracle={ 147 | name = "Oracle", 148 | text = { 149 | "Shop stocks an additional ", 150 | "free {C:tarot,T:p_arcana_normal_1}Arcana Pack{}", 151 | "every {C:attention}#1#{C:inactive} [#2#]{} rounds", 152 | }, 153 | unlock={ 154 | "Win a run with", 155 | "{C:attention}Fortune Teller{} on", 156 | "{C:attention}Gold Stake{} difficulty", 157 | }, 158 | }, 159 | pnr_partner_oracle_1={ 160 | name = "Oracle", 161 | text = { 162 | "Shop stocks an additional ", 163 | "free {C:tarot,T:p_arcana_normal_1}Arcana Pack{}", 164 | }, 165 | unlock={ 166 | "Win a run with", 167 | "{C:attention}Fortune Teller{} on", 168 | "{C:attention}Gold Stake{} difficulty", 169 | }, 170 | }, 171 | pnr_partner_finesse={ 172 | name = "Finesse", 173 | text = { 174 | "When {C:attention}Blind{} is selected,", 175 | "{C:attention}+#1#{} hand size this round", 176 | "if {C:attention}Joker{} slots are not full", 177 | }, 178 | unlock={ 179 | "Win a run with", 180 | "{C:attention}Juggler{} on", 181 | "{C:attention}Gold Stake{} difficulty", 182 | }, 183 | }, 184 | pnr_partner_finesse_1={ 185 | name = "Finesse", 186 | text = { 187 | "When {C:attention}Blind{} is selected,", 188 | "{C:attention}+#1#{} hand size this round", 189 | }, 190 | unlock={ 191 | "Win a run with", 192 | "{C:attention}Juggle{} onr", 193 | "{C:attention}Gold Stake{} difficulty", 194 | }, 195 | }, 196 | pnr_partner_gilded={ 197 | name = "Gilded", 198 | text = { 199 | "Earn {C:money}$#1#{} at end of round", 200 | "Payout increases by {C:money}$#2#{}", 201 | "when {C:attention}Boss Blind{} is defeated", 202 | }, 203 | unlock={ 204 | "Win a run with", 205 | "{C:attention}Golden Joker{} on", 206 | "{C:attention}Gold Stake{} difficulty", 207 | }, 208 | }, 209 | pnr_partner_batter={ 210 | name = "Batter", 211 | text = { 212 | "{C:green}Uncommon{} Jokers", 213 | "each give {C:mult}+#1#{} Mult", 214 | }, 215 | unlock={ 216 | "Win a run with", 217 | "{C:attention}Baseball Card{} on", 218 | "{C:attention}Gold Stake{} difficulty", 219 | }, 220 | }, 221 | pnr_partner_bargain={ 222 | name = "Bargain", 223 | text = { 224 | "If {C:attention}last discard{} of round", 225 | "has only {C:attention}1{} card, destroy ", 226 | "it and lose {C:money}$#1#{}", 227 | }, 228 | unlock={ 229 | "Win a run with", 230 | "{C:attention}Trading Card{} on", 231 | "{C:attention}Gold Stake{} difficulty", 232 | }, 233 | }, 234 | pnr_partner_bargain_1={ 235 | name = "Bargain", 236 | text = { 237 | "If {C:attention}last{} discard of round", 238 | "has {C:attention}2{} or fewer cards,", 239 | "destroy all of them", 240 | }, 241 | unlock={ 242 | "Win a run with", 243 | "{C:attention}Trading Card{} on", 244 | "{C:attention}Gold Stake{} difficulty", 245 | }, 246 | }, 247 | pnr_partner_memory={ 248 | name = "Memory", 249 | text = { 250 | "After each shop's first ", 251 | "{C:green}Reroll{}, the first card", 252 | "becomes {C:dark_edition,T:memory_negative}Negative{}", 253 | }, 254 | unlock={ 255 | "Win a run with", 256 | "{C:attention}Flash Card{} on", 257 | "{C:attention}Gold Stake{} difficulty", 258 | }, 259 | }, 260 | pnr_partner_memory_1={ 261 | name = "Memory", 262 | text = { 263 | "After each shop's first ", 264 | "{C:green}Reroll{}, the first card is", 265 | "free and becomes {C:dark_edition,T:memory_negative}Negative{}", 266 | }, 267 | unlock={ 268 | "Win a run with", 269 | "{C:attention}Flash Card{} on", 270 | "{C:attention}Gold Stake{} difficulty", 271 | }, 272 | }, 273 | pnr_partner_stoke={ 274 | name = "Stoke", 275 | text = { 276 | "{X:mult,C:white}X#1#{} Mult on next play", 277 | "Click to pay {C:money}$#3#{} and", 278 | "increase by {X:mult,C:white}X#2#{} Mult", 279 | }, 280 | unlock={ 281 | "Win a run with", 282 | "{C:attention}Campfire{} on", 283 | "{C:attention}Gold Stake{} difficulty", 284 | }, 285 | }, 286 | pnr_partner_verify={ 287 | name = "Verify", 288 | text = { 289 | "Click to pay {C:money}$#1#{} and", 290 | "add a random {C:attention}Seal{}", 291 | "to {C:attention}1{} selected card", 292 | }, 293 | unlock={ 294 | "Win a run with", 295 | "{C:attention}Certificate{} on", 296 | "{C:attention}Gold Stake{} difficulty", 297 | }, 298 | }, 299 | pnr_partner_jump={ 300 | name = "Jump", 301 | text = { 302 | "Gain {C:attention}#1#{} additional Tag", 303 | "when {C:attention}Blind{} is skipped", 304 | }, 305 | unlock={ 306 | "Win a run with", 307 | "{C:attention}Throwback{} on", 308 | "{C:attention}Gold Stake{} difficulty", 309 | }, 310 | }, 311 | pnr_partner_jump_1={ 312 | name = "Jump", 313 | text = { 314 | "Gain {C:attention}#1#{} additional Tags", 315 | "when {C:attention}Blind{} is skipped", 316 | }, 317 | unlock={ 318 | "Win a run with", 319 | "{C:attention}Throwback{} on", 320 | "{C:attention}Gold Stake{} difficulty", 321 | }, 322 | }, 323 | pnr_partner_vote={ 324 | name = "Vote", 325 | text = { 326 | "Retrigger {C:attention}first", 327 | "played card", 328 | "used in scoring", 329 | }, 330 | unlock={ 331 | "Win a run with", 332 | "{C:attention}Hanging Chad{} on", 333 | "{C:attention}Gold Stake{} difficulty", 334 | }, 335 | }, 336 | pnr_partner_vote_1={ 337 | name = "Vote", 338 | text = { 339 | "Retrigger {C:attention}first{} played", 340 | "card used in scoring", 341 | "{C:attention}#1#{} additional times", 342 | }, 343 | unlock={ 344 | "Win a run with", 345 | "{C:attention}Hanging Chad{} on", 346 | "{C:attention}Gold Stake{} difficulty", 347 | }, 348 | }, 349 | pnr_partner_bleed={ 350 | name = "Bleed", 351 | text = { 352 | "First played card with", 353 | "{C:hearts}Heart{} suit gives", 354 | "{X:mult,C:white}X#1#{} Mult when scored", 355 | }, 356 | unlock={ 357 | "Win a run with", 358 | "{C:attention}Bloodstone{} on", 359 | "{C:attention}Gold Stake{} difficulty", 360 | }, 361 | }, 362 | pnr_partner_andrew={ 363 | name = "Andrew", 364 | text = { 365 | "Click to pay {C:money}$#1#{}", 366 | "and gain {C:red}+#2#{} Discard", 367 | }, 368 | unlock={ 369 | "Win a run with", 370 | "{C:attention}Merry Andy{} on", 371 | "{C:attention}Gold Stake{} difficulty", 372 | }, 373 | }, 374 | pnr_partner_thrill={ 375 | name = "Thrill", 376 | text = { 377 | "{C:chips}+#1#{} Chips", 378 | }, 379 | unlock={ 380 | "Win a run with", 381 | "{C:attention}Stuntman{} on", 382 | "{C:attention}Gold Stake{} difficulty", 383 | }, 384 | }, 385 | pnr_partner_napkin={ 386 | name = "Napkin", 387 | text = { 388 | "{C:dark_edition}+#1#{} Joker Slot", 389 | }, 390 | unlock={ 391 | "Win a run with", 392 | "{C:attention}Brainstorm{} on", 393 | "{C:attention}Gold Stake{} difficulty", 394 | }, 395 | }, 396 | pnr_partner_napkin_1={ 397 | name = "Napkin", 398 | text = { 399 | "{C:dark_edition}+#1#{} Joker Slot", 400 | "Copies the ability", 401 | "of leftmost {C:attention}Joker{}", 402 | }, 403 | unlock={ 404 | "Win a run with", 405 | "{C:attention}Brainstorm{} on", 406 | "{C:attention}Gold Stake{} difficulty", 407 | }, 408 | }, 409 | pnr_partner_valid={ 410 | name = "Valid", 411 | text = { 412 | "{C:attention}First{} base scoring card", 413 | "each round becomes a", 414 | "random {C:enhanced}Enhanced{} card", 415 | }, 416 | unlock={ 417 | "Win a run with", 418 | "{C:attention}Driver License{} on", 419 | "{C:attention}Gold Stake{} difficulty", 420 | }, 421 | }, 422 | pnr_partner_valid_1={ 423 | name = "Valid", 424 | text = { 425 | "{C:attention}First{} base scoring card", 426 | "each play becomes a", 427 | "random {C:enhanced}Enhanced{} card", 428 | }, 429 | unlock={ 430 | "Win a run with", 431 | "{C:attention}Driver License{} on", 432 | "{C:attention}Gold Stake{} difficulty", 433 | }, 434 | }, 435 | pnr_partner_blaze={ 436 | name = "Blaze", 437 | text = { 438 | "{C:green}#1# in #2#{} chance to", 439 | "upgrade the level of", 440 | "{C:attention}discarded{} poker hand", 441 | }, 442 | unlock={ 443 | "Win a run with", 444 | "{C:attention}Burnt Joker{} on", 445 | "{C:attention}Gold Stake{} difficulty", 446 | }, 447 | }, 448 | }, 449 | Other={ 450 | memory_negative={ 451 | name="Negative", 452 | text={ 453 | "{C:dark_edition}+#1#{} Card Slot", 454 | }, 455 | }, 456 | }, 457 | Mod={ 458 | partner={ 459 | name = "Partner-API", 460 | text = { 461 | "Add {C:red}24{} Partners modeled after Jokers", 462 | " ", 463 | " ", 464 | "{C:gold}Credits{}", 465 | "Baimao, Betmma,", 466 | "Brookling and Snowylight", 467 | " ", 468 | " ", 469 | "{C:green}Additional Thanks{}", 470 | "Clay, Gainumki,", 471 | "Larswijn, Spriter", 472 | "and Theonegoofali", 473 | " ", 474 | " ", 475 | "Click to view the {C:blue}wiki{} page: ", 476 | "{C:attention}https://balatromods.miraheze.org/wiki/Partner{}", 477 | }, 478 | }, 479 | }, 480 | }, 481 | misc={ 482 | dictionary={ 483 | b_partners="Partners", 484 | b_partner_agree="Accept", 485 | b_partner_random="Random", 486 | b_partner_skip="Skip", 487 | b_open_partner_wiki="Click to open the Partner Wiki", 488 | k_partner="Partner", 489 | k_partner_buffed_level="Buffed lvl.", 490 | k_enable_partner="Enable Partner", 491 | k_enable_speech_bubble="Enable Speech Bubble", 492 | k_temporary_unlock_all="Temporarily Unlock All", 493 | k_partner_destroyed="Destroyed!", 494 | k_partner_enhanced="Enhanced!", 495 | ml_partner_unique_ability={ 496 | "Cards may enhance the abilities of", 497 | "corresponding partners", 498 | }, 499 | }, 500 | quips={ 501 | pnr_1={ 502 | "Hello,", 503 | "My friend!", 504 | }, 505 | pnr_2={ 506 | "I will stay", 507 | "with you!", 508 | }, 509 | pnr_3={ 510 | "I think you will have an", 511 | "outstanding performance!", 512 | }, 513 | pnr_4={ 514 | "I believe you can", 515 | "win this run!", 516 | }, 517 | pnr_5={ 518 | "Professional partner", 519 | "always come prepared", 520 | }, 521 | pnr_6={ 522 | "Credits:", 523 | "Brookling", 524 | "Snowylight", 525 | "Betmma", 526 | }, 527 | pnr_partner_mute_1={ 528 | ":)" 529 | }, 530 | pnr_partner_mute_2={ 531 | ":D" 532 | }, 533 | pnr_partner_mute_3={ 534 | ":P" 535 | }, 536 | pnr_partner_mute_4={ 537 | ":I" 538 | }, 539 | pnr_partner_mute_5={ 540 | "XD" 541 | }, 542 | pnr_partner_mute_6={ 543 | ":l" 544 | }, 545 | pnr_partner_jump_1={ 546 | "Cool Beans" 547 | }, 548 | pnr_partner_jump_2={ 549 | "Remember, if there's a blind", 550 | "you're not digging the vibe of:", 551 | "SAY NO AND SKIP!" 552 | }, 553 | pnr_partner_jump_3={ 554 | "Totally Tubular!" 555 | }, 556 | pnr_partner_jump_4={ 557 | "Gnarly!" 558 | }, 559 | pnr_partner_jump_5={ 560 | "Let's make some", 561 | "bodacious plays!" 562 | }, 563 | pnr_partner_jump_6={ 564 | "Hey-yo!", 565 | "Freshest partner in town,", 566 | "reporting for duty!" 567 | }, 568 | }, 569 | }, 570 | } 571 | -------------------------------------------------------------------------------- /localization/zh_CN.lua: -------------------------------------------------------------------------------- 1 | return { 2 | descriptions = { 3 | Partner={ 4 | pnr_partner_jimbo={ 5 | name = "金宝伙伴", 6 | text = { 7 | "每次出牌", 8 | "获得{C:chips}+#2#{}筹码", 9 | "{C:inactive}(当前为{C:chips}+#1#{C:inactive}筹码)", 10 | }, 11 | unlock={ 12 | "使用{C:attention}小丑", 13 | "在{C:attention}金注", 14 | "难度下获胜", 15 | }, 16 | }, 17 | pnr_partner_mute={ 18 | name = "哑剧伙伴", 19 | text = { 20 | "重复触发手中", 21 | "{C:attention}最后一张{}牌的能力", 22 | }, 23 | unlock={ 24 | "使用{C:attention}哑剧演员", 25 | "在{C:attention}金注", 26 | "难度下获胜", 27 | }, 28 | }, 29 | pnr_partner_mute_1={ 30 | name = "哑剧伙伴", 31 | text = { 32 | "手中{C:attention}最后一张{}牌", 33 | "能力额外触发{C:attention}#1#{}次", 34 | }, 35 | unlock={ 36 | "使用{C:attention}哑剧演员", 37 | "在{C:attention}金注", 38 | "难度下获胜", 39 | }, 40 | }, 41 | pnr_partner_unite={ 42 | name = "拳头伙伴", 43 | text = { 44 | "手中点数{C:attention}最大{}的牌", 45 | "会给予{X:mult,C:white}X#1#{}倍率", 46 | }, 47 | unlock={ 48 | "使用{C:attention}致胜之拳", 49 | "在{C:attention}金注", 50 | "难度下获胜", 51 | }, 52 | }, 53 | pnr_partner_hatch={ 54 | name = "鸡蛋伙伴", 55 | text = { 56 | "回合结束时", 57 | "令所有{C:attention}小丑牌{}", 58 | "{C:attention}售价{}增加{C:money}$#1#{}", 59 | }, 60 | unlock={ 61 | "使用{C:attention}鸡蛋", 62 | "在{C:attention}金注", 63 | "难度下获胜", 64 | }, 65 | }, 66 | pnr_partner_steal={ 67 | name = "窃贼伙伴", 68 | text = { 69 | "选择{C:attention}Boss盲注{}后", 70 | "出牌次数{C:blue}+#1#{}", 71 | }, 72 | unlock={ 73 | "使用{C:attention}窃贼", 74 | "在{C:attention}金注", 75 | "难度下获胜", 76 | }, 77 | }, 78 | pnr_partner_steal_1={ 79 | name = "窃贼伙伴", 80 | text = { 81 | "选择{C:attention}盲注{}后", 82 | "弃牌次数{C:red}+#1#{}", 83 | }, 84 | unlock={ 85 | "使用{C:attention}窃贼", 86 | "在{C:attention}金注", 87 | "难度下获胜", 88 | }, 89 | }, 90 | pnr_partner_pale={ 91 | name = "无面伙伴", 92 | text = { 93 | "每弃掉{C:attention}#1#{C:inactive}[#2#]{}张{C:attention}人头牌{}", 94 | "获得{C:money}$#3#{}", 95 | }, 96 | unlock={ 97 | "使用{C:attention}无面小丑", 98 | "在{C:attention}金注", 99 | "难度下获胜", 100 | }, 101 | }, 102 | pnr_partner_penalty={ 103 | name = "红牌伙伴", 104 | text = { 105 | "跳过{C:attention}补充包{}时", 106 | "返还{C:attention}一半{}购买费用", 107 | }, 108 | unlock={ 109 | "使用{C:attention}红牌", 110 | "在{C:attention}金注", 111 | "难度下获胜", 112 | }, 113 | }, 114 | pnr_partner_penalty_1={ 115 | name = "红牌伙伴", 116 | text = { 117 | "跳过{C:attention}补充包{}时", 118 | "返还{C:attention}全额{}购买费用", 119 | }, 120 | unlock={ 121 | "使用{C:attention}红牌", 122 | "在{C:attention}金注", 123 | "难度下获胜", 124 | }, 125 | }, 126 | pnr_partner_fantasy={ 127 | name = "幻觉伙伴", 128 | text = { 129 | "打开{C:attention}补充包{}有{C:green}#1#/#2#{}几率", 130 | "生成一张{C:spectral}幻灵牌{}", 131 | "{C:inactive}(必须有空间)", 132 | }, 133 | unlock={ 134 | "使用{C:attention}幻觉", 135 | "在{C:attention}金注", 136 | "难度下获胜", 137 | }, 138 | }, 139 | pnr_partner_oracle={ 140 | name = "占卜伙伴", 141 | text = { 142 | "每{C:attention}#1#{C:inactive}[#2#]{}个商店", 143 | "额外上架一个", 144 | "免费的{C:tarot,T:p_arcana_normal_1}秘术包{}", 145 | }, 146 | unlock={ 147 | "使用{C:attention}占卜师", 148 | "在{C:attention}金注", 149 | "难度下获胜", 150 | }, 151 | }, 152 | pnr_partner_oracle_1={ 153 | name = "占卜伙伴", 154 | text = { 155 | "每个商店额外上架", 156 | "一个免费{C:tarot,T:p_arcana_normal_1}秘术包{}", 157 | }, 158 | unlock={ 159 | "使用{C:attention}占卜师", 160 | "在{C:attention}金注", 161 | "难度下获胜", 162 | }, 163 | }, 164 | pnr_partner_finesse={ 165 | name = "杂耍伙伴", 166 | text = { 167 | "选择{C:attention}盲注{}后", 168 | "若{C:attention}小丑牌槽位{}未满", 169 | "本回合手牌上限{C:attention}+#1#{}", 170 | }, 171 | unlock={ 172 | "使用{C:attention}杂耍师", 173 | "在{C:attention}金注", 174 | "难度下获胜", 175 | }, 176 | }, 177 | pnr_partner_finesse_1={ 178 | name = "杂耍伙伴", 179 | text = { 180 | "选择{C:attention}盲注{}后", 181 | "本回合手牌上限{C:attention}+#1#{}", 182 | }, 183 | unlock={ 184 | "使用{C:attention}杂耍师", 185 | "在{C:attention}金注", 186 | "难度下获胜", 187 | }, 188 | }, 189 | pnr_partner_gilded={ 190 | name = "黄金伙伴", 191 | text = { 192 | "回合结束时获得{C:money}$#1#{}", 193 | "击败{C:attention}Boss盲注{}后", 194 | "这一金额增加{C:money}$#2#{}", 195 | }, 196 | unlock={ 197 | "使用{C:attention}黄金小丑", 198 | "在{C:attention}金注", 199 | "难度下获胜", 200 | }, 201 | }, 202 | pnr_partner_batter={ 203 | name = "棒球伙伴", 204 | text = { 205 | "每张{C:green}罕见{}小丑牌", 206 | "会给予{C:mult}+#1#{}倍率", 207 | }, 208 | unlock={ 209 | "使用{C:attention}棒球卡", 210 | "在{C:attention}金注", 211 | "难度下获胜", 212 | }, 213 | }, 214 | pnr_partner_bargain={ 215 | name = "交易伙伴", 216 | text = { 217 | "每回合{C:attention}最后一次{}弃牌", 218 | "若只有一张牌", 219 | "则将其摧毁并失去{C:money}$#1#{}", 220 | }, 221 | unlock={ 222 | "使用{C:attention}交易卡", 223 | "在{C:attention}金注", 224 | "难度下获胜", 225 | }, 226 | }, 227 | pnr_partner_bargain_1={ 228 | name = "交易伙伴", 229 | text = { 230 | "每回合{C:attention}最后一次{}弃牌", 231 | "若不超过两张牌", 232 | "则摧毁{C:attention}所有{}弃掉的牌", 233 | }, 234 | unlock={ 235 | "使用{C:attention}交易卡", 236 | "在{C:attention}金注", 237 | "难度下获胜", 238 | }, 239 | }, 240 | pnr_partner_memory={ 241 | name = "闪示伙伴", 242 | text = { 243 | "令每个商店第一次", 244 | "{C:green}重掷{}后的第一张牌", 245 | "变成{C:dark_edition,T:memory_negative}负片{}版本", 246 | }, 247 | unlock={ 248 | "使用{C:attention}闪示卡", 249 | "在{C:attention}金注", 250 | "难度下获胜", 251 | }, 252 | }, 253 | pnr_partner_memory_1={ 254 | name = "闪示伙伴", 255 | text = { 256 | "令每个商店第一次", 257 | "{C:green}重掷{}后的第一张牌", 258 | "变成免费的{C:dark_edition}负片{}版本", 259 | }, 260 | unlock={ 261 | "使用{C:attention}闪示卡", 262 | "在{C:attention}金注", 263 | "难度下获胜", 264 | }, 265 | }, 266 | pnr_partner_stoke={ 267 | name = "篝火伙伴", 268 | text = { 269 | "下次出牌{X:mult,C:white}X#1#{}倍率", 270 | "点击支付{C:money}$#3#{}", 271 | "以提高{X:mult,C:white}X#2#{}倍率", 272 | }, 273 | unlock={ 274 | "使用{C:attention}篝火", 275 | "在{C:attention}金注", 276 | "难度下获胜", 277 | }, 278 | }, 279 | pnr_partner_verify={ 280 | name = "证书伙伴", 281 | text = { 282 | "点击支付{C:money}$#1#{}", 283 | "为选中的{C:attention}1{}张牌", 284 | "添加随机{C:attention}蜡封{}", 285 | }, 286 | unlock={ 287 | "使用{C:attention}证书", 288 | "在{C:attention}金注", 289 | "难度下获胜", 290 | }, 291 | }, 292 | pnr_partner_jump={ 293 | name = "回溯伙伴", 294 | text = { 295 | "跳过{C:attention}盲注{}时", 296 | "额外获得{C:attention}#1#{}份标签", 297 | }, 298 | unlock={ 299 | "使用{C:attention}回溯", 300 | "在{C:attention}金注", 301 | "难度下获胜", 302 | }, 303 | }, 304 | pnr_partner_jump_1={ 305 | name = "回溯伙伴", 306 | text = { 307 | "跳过{C:attention}盲注{}时", 308 | "额外获得{C:attention}#1#{}份标签", 309 | }, 310 | unlock={ 311 | "使用{C:attention}回溯", 312 | "在{C:attention}金注", 313 | "难度下获胜", 314 | }, 315 | }, 316 | pnr_partner_vote={ 317 | name = "选票伙伴", 318 | text = { 319 | "重复触发", 320 | "{C:attention}第一张{}计分牌", 321 | }, 322 | unlock={ 323 | "使用{C:attention}未断选票", 324 | "在{C:attention}金注", 325 | "难度下获胜", 326 | }, 327 | }, 328 | pnr_partner_vote_1={ 329 | name = "选票伙伴", 330 | text = { 331 | "{C:attention}第一张{}计分牌", 332 | "额外触发{C:attention}#1#{}次", 333 | }, 334 | unlock={ 335 | "使用{C:attention}未断选票", 336 | "在{C:attention}金注", 337 | "难度下获胜", 338 | }, 339 | }, 340 | pnr_partner_bleed={ 341 | name = "血石伙伴", 342 | text = { 343 | "打出的第一张", 344 | "{C:hearts}红桃牌{}在计分时", 345 | "给予{X:mult,C:white}X#1#{}倍率", 346 | }, 347 | unlock={ 348 | "使用{C:attention}血石", 349 | "在{C:attention}金注", 350 | "难度下获胜", 351 | }, 352 | }, 353 | pnr_partner_andrew={ 354 | name = "安迪伙伴", 355 | text = { 356 | "点击支付{C:money}$#1#{}", 357 | "弃牌次数{C:red}+#2#{}", 358 | }, 359 | unlock={ 360 | "使用{C:attention}快乐安迪", 361 | "在{C:attention}金注", 362 | "难度下获胜", 363 | }, 364 | }, 365 | pnr_partner_thrill={ 366 | name = "特技伙伴", 367 | text = { 368 | "{C:chips}+#1#{}筹码", 369 | }, 370 | unlock={ 371 | "使用{C:attention}特技演员", 372 | "在{C:attention}金注", 373 | "难度下获胜", 374 | }, 375 | }, 376 | pnr_partner_napkin={ 377 | name = "头脑伙伴", 378 | text = { 379 | "小丑牌槽位{C:dark_edition}+#1#{}", 380 | }, 381 | unlock={ 382 | "使用{C:attention}头脑风暴", 383 | "在{C:attention}金注", 384 | "难度下获胜", 385 | }, 386 | }, 387 | pnr_partner_napkin_1={ 388 | name = "头脑伙伴", 389 | text = { 390 | "小丑牌槽位{C:dark_edition}+#1#{}", 391 | "复制最左边的", 392 | "{C:attention}小丑牌的能力{}", 393 | }, 394 | unlock={ 395 | "使用{C:attention}头脑风暴", 396 | "在{C:attention}金注", 397 | "难度下获胜", 398 | }, 399 | }, 400 | pnr_partner_valid={ 401 | name = "驾照伙伴", 402 | text = { 403 | "令每回合{C:attention}第一张{}", 404 | "基础计分牌", 405 | "变成随机{C:enhanced}增强牌{}", 406 | }, 407 | unlock={ 408 | "使用{C:attention}驾驶执照", 409 | "在{C:attention}金注", 410 | "难度下获胜", 411 | }, 412 | }, 413 | pnr_partner_valid_1={ 414 | name = "驾照伙伴", 415 | text = { 416 | "每次出牌", 417 | "令{C:attention}第一张{}基础计分牌", 418 | "变成随机{C:enhanced}增强牌{}", 419 | }, 420 | unlock={ 421 | "使用{C:attention}驾驶执照", 422 | "在{C:attention}金注", 423 | "难度下获胜", 424 | }, 425 | }, 426 | pnr_partner_blaze={ 427 | name = "烧焦伙伴", 428 | text = { 429 | "有{C:green}#1#/#2#{}几率升级", 430 | "弃掉的{C:attention}牌型等级{}", 431 | }, 432 | unlock={ 433 | "使用{C:attention}烧焦小丑", 434 | "在{C:attention}金注", 435 | "难度下获胜", 436 | }, 437 | }, 438 | }, 439 | Other={ 440 | memory_negative={ 441 | name="负片", 442 | text={ 443 | "{C:dark_edition}+#1#{}卡牌槽位", 444 | }, 445 | }, 446 | }, 447 | Mod={ 448 | partner={ 449 | name = "伙伴API", 450 | text = { 451 | "新增{C:red}24{}个以小丑为原型的伙伴", 452 | " ", 453 | " ", 454 | "{C:gold}制作组{}", 455 | "Baimao,Betmma,", 456 | "Brookling 和 Snowylight", 457 | " ", 458 | " ", 459 | "{C:green}感谢{}", 460 | "Clay,Gainumki,", 461 | "Larswijn,Spriter", 462 | "和 Theonegoofali", 463 | " ", 464 | " ", 465 | "点击浏览{C:blue}wiki{}页面:", 466 | "{C:attention}https://balatromods.miraheze.org/wiki/Partner{}", 467 | } 468 | } 469 | }, 470 | }, 471 | misc={ 472 | dictionary={ 473 | b_partners="伙伴", 474 | b_partner_agree="选择伙伴", 475 | b_partner_random="随机伙伴", 476 | b_partner_skip="忽略伙伴", 477 | b_open_partner_wiki="点击打开伙伴维基", 478 | k_partner="伙伴", 479 | k_partner_buffed_level="增益等级", 480 | k_enable_partner="启用伙伴", 481 | k_enable_speech_bubble="启用对话气泡", 482 | k_temporary_unlock_all="临时解锁全部", 483 | k_partner_destroyed="摧毁!", 484 | k_partner_enhanced="增强!", 485 | ml_partner_unique_ability={ 486 | "小丑可能会强化", 487 | "对应伙伴的能力", 488 | }, 489 | }, 490 | quips={ 491 | pnr_1={ 492 | "你好,", 493 | "我的朋友!", 494 | }, 495 | pnr_2={ 496 | "我会一直", 497 | "和你在一起的!", 498 | }, 499 | pnr_3={ 500 | "相信你能有", 501 | "亮眼的表现!", 502 | }, 503 | pnr_4={ 504 | "这局我觉得", 505 | "你能赢!", 506 | }, 507 | pnr_5={ 508 | "专业伙伴", 509 | "总是有备而来", 510 | }, 511 | pnr_6={ 512 | "鸣谢:", 513 | "Brookling", 514 | "Snowylight", 515 | "Betmma", 516 | }, 517 | pnr_partner_mute_1={ 518 | ":)" 519 | }, 520 | pnr_partner_mute_2={ 521 | ":D" 522 | }, 523 | pnr_partner_mute_3={ 524 | ":P" 525 | }, 526 | pnr_partner_mute_4={ 527 | ":I" 528 | }, 529 | pnr_partner_mute_5={ 530 | "XD" 531 | }, 532 | pnr_partner_mute_6={ 533 | ":l" 534 | }, 535 | pnr_partner_jump_1={ 536 | "太酷啦!" 537 | }, 538 | pnr_partner_jump_2={ 539 | "如果你不喜欢", 540 | "某些盲注", 541 | "说不然后跳过!" 542 | }, 543 | pnr_partner_jump_3={ 544 | "明智的选择!" 545 | }, 546 | pnr_partner_jump_4={ 547 | "冲啊!" 548 | }, 549 | pnr_partner_jump_5={ 550 | "让我们", 551 | "一起摇摆!" 552 | }, 553 | pnr_partner_jump_6={ 554 | "芜湖!", 555 | "最机灵的伙伴", 556 | "来报到啦!" 557 | }, 558 | }, 559 | }, 560 | } 561 | -------------------------------------------------------------------------------- /lovely.toml: -------------------------------------------------------------------------------- 1 | [manifest] 2 | version = "1.0.2c" 3 | dump_lua = true 4 | priority = 0 5 | 6 | [[patches]] 7 | [patches.pattern] 8 | target = "functions/state_events.lua" 9 | pattern = '''-- context.final_scoring_step calculations''' 10 | position = "before" 11 | payload = ''' 12 | 13 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 14 | local ret = G.GAME.selected_partner_card:calculate_partner({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, joker_main = true}) 15 | if ret and ret.duplication then 16 | for k, v in ipairs(ret) do 17 | SMODS.trigger_effects({{individual = v}}, G.GAME.selected_partner_card) 18 | end 19 | elseif ret then 20 | SMODS.trigger_effects({{individual = ret}}, G.GAME.selected_partner_card) 21 | end 22 | end 23 | 24 | ''' 25 | match_indent = false 26 | overwrite = false 27 | 28 | [[patches]] 29 | [patches.pattern] 30 | target = "functions/state_events.lua" 31 | pattern = '''if G.GAME.dollars >= 5 and not G.GAME.modifiers.no_interest then''' 32 | position = "before" 33 | payload = ''' 34 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 35 | local ret = G.GAME.selected_partner_card:calculate_partner_cash() 36 | if ret then 37 | add_round_eval_row({dollars = ret, bonus = true, name = "partner", pitch = pitch, card = G.GAME.selected_partner_card}) 38 | pitch = pitch + 0.06 39 | dollars = dollars + ret 40 | end 41 | end 42 | ''' 43 | match_indent = false 44 | overwrite = false 45 | 46 | [[patches]] 47 | [patches.pattern] 48 | target = "functions/common_events.lua" 49 | pattern = '''elseif config.name == 'interest' then''' 50 | position = "before" 51 | payload = ''' 52 | elseif string.find(config.name, "partner") then 53 | table.insert(left_text, {n=G.UIT.O, config={object = DynaText({string = localize{type = "name_text", set = config.card.config.center.set, key = config.card.config.center.key}, colours = {G.C.FILTER}, shadow = true, pop_in = 0, scale = 0.6*scale, silent = true})}}) 54 | ''' 55 | match_indent = false 56 | overwrite = false 57 | 58 | [[patches]] 59 | [patches.pattern] 60 | target = "functions/state_events.lua" 61 | pattern = '''-- Calculate context.other_joker effects''' 62 | position = "before" 63 | payload = ''' 64 | 65 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 66 | local other_key = "other_unknown" 67 | if _card.ability.set == "Joker" then other_key = "other_joker" end 68 | if _card.ability.consumeable then other_key = "other_consumeable" end 69 | if _card.ability.set == "Voucher" then other_key = "other_voucher" end 70 | local ret = G.GAME.selected_partner_card:calculate_partner({full_hand = G.play.cards, scoring_hand = scoring_hand, scoring_name = text, poker_hands = poker_hands, [other_key] = _card, other_main = _card}) 71 | if ret and ret.duplication then 72 | for k, v in ipairs(ret) do 73 | table.insert(effects, {individual = v}) 74 | end 75 | elseif ret then 76 | table.insert(effects, {individual = ret}) 77 | end 78 | end 79 | 80 | ''' 81 | match_indent = false 82 | overwrite = false 83 | 84 | [[patches]] 85 | [patches.pattern] 86 | target = "functions/misc_functions.lua" 87 | pattern = '''elseif part.control.X or part.control.B then''' 88 | position = "before" 89 | payload = ''' 90 | elseif part.control.T and part.control.T == "memory_negative" then 91 | final_line[#final_line+1] = {n=G.UIT.T, config={ 92 | detailed_tooltip = {key = part.control.T, set = "Other", vars = {1}} or nil, 93 | text = assembled_string, 94 | shadow = args.shadow, 95 | colour = part.control.V and args.vars.colours[tonumber(part.control.V)] or not part.control.C and args.text_colour or loc_colour(part.control.C or nil, args.default_col), 96 | scale = 0.32*(part.control.s and tonumber(part.control.s) or args.scale or 1)*desc_scale},} 97 | ''' 98 | match_indent = false 99 | overwrite = false 100 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "partner", 3 | "name": "Partner", 4 | "display_name": "partner", 5 | "author": ["baimao"], 6 | "description": "Add interesting partners to your Balatro. Thanks to brookling, snowylight, betmma", 7 | "prefix": "partner", 8 | "main_file": "partner.lua", 9 | "priority": -10, 10 | "badge_colour": "366999", 11 | "version": "1.0.2c", 12 | "dependencies": [ 13 | "Steamodded (>=1.0.0~BETA-0323b)", 14 | "Lovely (>=0.6)", 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /partner.lua: -------------------------------------------------------------------------------- 1 | -- Extend Page 2 | 3 | Partner_API = SMODS.current_mod 4 | 5 | Partner_API.Partner = SMODS.Center:extend{ 6 | unlocked = true, 7 | discovered = false, 8 | no_quips = false, 9 | individual_quips = false, 10 | config = {}, 11 | link_config = {}, 12 | set = "Partner", 13 | class_prefix = "pnr", 14 | required_params = {"key", "atlas", "pos"}, 15 | pre_inject_class = function(self) 16 | G.P_CENTER_POOLS[self.set] = {} 17 | end, 18 | set_card_type_badge = function(self, card, badges) 19 | local display_info = {localize("k_partner")} 20 | if self.badges_info and next(self.badges_info) then 21 | for _, v in pairs(self.badges_info) do 22 | if localize(v) ~= "ERROR" then 23 | display_info[#display_info+1] = localize(v) 24 | else 25 | display_info[#display_info+1] = v 26 | end 27 | end 28 | end 29 | badges[#badges+1] = create_badge(display_info, G.C.DARK_EDITION, G.C.WHITE) 30 | end, 31 | generate_ui = function(self, info_queue, card, desc_nodes, specific_vars, full_UI_table) 32 | SMODS.Center.generate_ui(self, info_queue, card, desc_nodes, specific_vars, full_UI_table) 33 | if next(self.link_config) then 34 | local link_level = nil 35 | for k, v in pairs(self.link_config) do 36 | if next(SMODS.find_card(k)) and (not link_level or link_level < v) then 37 | link_level = v 38 | end 39 | end 40 | if link_level then 41 | local main_end = {{n=G.UIT.C, config={align = "bm"}, nodes={ 42 | {n=G.UIT.O, config={object = DynaText({string = {"<"..localize("k_partner_buffed_level")..link_level..">"}, colours = {G.C.DARK_EDITION}, float = true, scale = 0.3})}}, 43 | }}} 44 | desc_nodes[#desc_nodes+1] = main_end 45 | end 46 | end 47 | end 48 | } 49 | 50 | -- Collection Page 51 | 52 | Partner_API.custom_collection_tabs = function() 53 | local tally = 0 54 | for _, v in pairs(G.P_CENTER_POOLS["Partner"]) do 55 | if v:is_unlocked() then 56 | tally = tally + 1 57 | end 58 | end 59 | return {UIBox_button({button = "your_collection_partners", label = {localize("b_partners")}, count = {tally = tally, of = #G.P_CENTER_POOLS["Partner"]}, minw = 5, id = "your_collection_partners"})} 60 | end 61 | 62 | G.FUNCS.your_collection_partners = function(e) 63 | G.SETTINGS.paused = true 64 | G.FUNCS.overlay_menu{ 65 | definition = create_UIBox_your_collection_partners(), 66 | } 67 | end 68 | 69 | function create_UIBox_your_collection_partners() 70 | local deck_tables = {} 71 | G.your_collection = {} 72 | for j = 1, 2 do 73 | G.your_collection[j] = CardArea(G.ROOM.T.x, G.ROOM.T.h, 3.6*G.CARD_W, 0.7*G.CARD_H, {card_limit = 4, type = "title", highlight_limit = 0, collection = true}) 74 | table.insert(deck_tables, {n=G.UIT.R, config={align = "cm", padding = 0.07, no_fill = true}, nodes={ 75 | {n=G.UIT.O, config={object = G.your_collection[j]}} 76 | }}) 77 | end 78 | local partner_options = {} 79 | for i = 1, math.ceil(#G.P_CENTER_POOLS["Partner"]/(4*#G.your_collection)) do 80 | table.insert(partner_options, localize("k_page").." "..tostring(i).."/"..tostring(math.ceil(#G.P_CENTER_POOLS["Partner"]/(4*#G.your_collection)))) 81 | end 82 | for i = 1, 4 do 83 | for j = 1, #G.your_collection do 84 | local center = G.P_CENTER_POOLS["Partner"][i+(j-1)*4] 85 | if not center then break end 86 | local card = Card(G.your_collection[j].T.x+G.your_collection[j].T.w/2, G.your_collection[j].T.y, G.CARD_W*46/71, G.CARD_H*58/95, nil, center) 87 | --card.sticker = get_joker_win_sticker(center) 88 | G.your_collection[j]:emplace(card) 89 | end 90 | end 91 | INIT_COLLECTION_CARD_ALERTS() 92 | local t = create_UIBox_generic_options({back_func = "your_collection_other_gameobjects", infotip = localize("ml_partner_unique_ability"), contents = { 93 | {n=G.UIT.R, config={align = "cm", r = 0.1, colour = G.C.BLACK, emboss = 0.05}, nodes=deck_tables}, 94 | {n=G.UIT.R, config={align = "cm"}, nodes={ 95 | create_option_cycle({options = partner_options, w = 4.5, cycle_shoulders = true, opt_callback = "your_collection_partner_page", current_option = 1, colour = G.C.RED, no_pips = true, focus_args = {snap_to = true, nav = "wide"}}) 96 | }} 97 | }}) 98 | return t 99 | end 100 | 101 | G.FUNCS.your_collection_partner_page = function(args) 102 | if not args or not args.cycle_config then return end 103 | for j = 1, #G.your_collection do 104 | for i = #G.your_collection[j].cards, 1, -1 do 105 | local c = G.your_collection[j]:remove_card(G.your_collection[j].cards[i]) 106 | c:remove() 107 | c = nil 108 | end 109 | end 110 | for i = 1, 4 do 111 | for j = 1, #G.your_collection do 112 | local center = G.P_CENTER_POOLS["Partner"][i+(j-1)*4+(4*#G.your_collection*(args.cycle_config.current_option-1))] 113 | if not center then break end 114 | local card = Card(G.your_collection[j].T.x+G.your_collection[j].T.w/2, G.your_collection[j].T.y, G.CARD_W*46/71, G.CARD_H*58/95, G.P_CARDS.empty, center) 115 | --card.sticker = get_joker_win_sticker(center) 116 | G.your_collection[j]:emplace(card) 117 | end 118 | end 119 | INIT_COLLECTION_CARD_ALERTS() 120 | end 121 | 122 | -- UI Page 123 | 124 | function Partner_API.Partner:get_link_level() 125 | if next(self.link_config) then 126 | local link_level = nil 127 | for k, v in pairs(self.link_config) do 128 | if next(SMODS.find_card(k)) and (not link_level or link_level < v) then 129 | link_level = v 130 | end 131 | end 132 | return link_level 133 | end 134 | end 135 | 136 | function Partner_API.Partner:is_unlocked() 137 | return self.unlocked or Partner_API.config.temporary_unlock_all or G.PROFILES[G.SETTINGS.profile].all_unlocked 138 | end 139 | 140 | Partner_API.description_loc_vars = function() 141 | return {background_colour = G.C.CLEAR, text_colour = G.C.WHITE, scale = 1.2, shadow = true} 142 | end 143 | 144 | Partner_API.custom_ui = function(nodes) 145 | local _, description = unpack(nodes) 146 | local wiki_deepfind = SMODS.deepfind(description, "https://balatromods.miraheze.org/wiki/Partner", true)[1] 147 | if wiki_deepfind then 148 | local wiki_link_table = wiki_deepfind.objtree[#wiki_deepfind.objtree-2] 149 | wiki_link_table.config.button = "open_partner_wiki" 150 | wiki_link_table.config.tooltip = {text = {localize("b_open_partner_wiki")}} 151 | end 152 | end 153 | 154 | Partner_API.config_tab = function() 155 | return {n=G.UIT.ROOT, config = {align = "cm", padding = 0.05, colour = G.C.CLEAR}, nodes={ 156 | create_toggle({label = localize("k_enable_partner"), ref_table = Partner_API.config, ref_value = "enable_partner"}), 157 | create_toggle({label = localize("k_enable_speech_bubble"), ref_table = Partner_API.config, ref_value = "enable_speech_bubble"}), 158 | create_toggle({label = localize("k_temporary_unlock_all"), ref_table = Partner_API.config, ref_value = "temporary_unlock_all"}), 159 | }} 160 | end 161 | 162 | G.FUNCS.open_partner_wiki = function(e) 163 | love.system.openURL("https://balatromods.miraheze.org/wiki/Partner") 164 | end 165 | 166 | local Card_set_sprites_ref = Card.set_sprites 167 | function Card:set_sprites(_center, _front) 168 | Card_set_sprites_ref(self, _center, _front) 169 | if _center and _center.set == "Partner" and not _center:is_unlocked() then 170 | self.children.center.atlas = G.ASSET_ATLAS["partner_Partner"] 171 | self.children.center:set_sprite_pos({x = 0, y = 4}) 172 | end 173 | end 174 | 175 | local generate_card_ui_ref = generate_card_ui 176 | function generate_card_ui(_c, full_UI_table, specific_vars, card_type, badges, hide_desc, main_start, main_end, card) 177 | if _c and _c.set == "Partner" and _c:is_unlocked() and card_type and card_type == "Locked" and (specific_vars and not specific_vars.no_name or not specific_vars) then card_type = "Partner" end 178 | if _c and _c.set == "Partner" and _c:is_unlocked() and badges then badges.card_type = "Partner" end 179 | return generate_card_ui_ref(_c, full_UI_table, specific_vars, card_type, badges, hide_desc, main_start, main_end, card) 180 | end 181 | 182 | local Card_update_ref = Card.update 183 | function Card:update(dt) 184 | Card_update_ref(self, dt) 185 | if self.ability.set == "Partner" and not self.states.drag.is then 186 | if self.T.x+self.T.w > G.ROOM.T.w then 187 | self.T.x = G.ROOM.T.w-self.T.w 188 | elseif self.T.x < 0 then 189 | self.T.x = 0 190 | end 191 | if self.T.y+self.T.h > G.ROOM.T.h then 192 | self.T.y = G.ROOM.T.h-self.T.h 193 | elseif self.T.y < 0 then 194 | self.T.y = 0 195 | end 196 | end 197 | end 198 | 199 | local create_UIBox_card_unlock_ref = create_UIBox_card_unlock 200 | function create_UIBox_card_unlock(card_center) 201 | local ret = create_UIBox_card_unlock_ref(card_center) 202 | local title = ret.nodes[1].nodes[1].nodes[1].nodes[1].nodes[1].nodes[1].nodes[1].nodes[1].config 203 | if card_center.set == "Partner" then 204 | title.object:remove() 205 | title.object = DynaText({string = {localize("k_partner")}, colours = {G.C.BLUE}, shadow = true, rotate = true, bump = true, pop_in = 0.3, pop_in_rate = 2, scale = 1.2}) 206 | end 207 | return ret 208 | end 209 | 210 | local create_UIBox_notify_alert_ref = create_UIBox_notify_alert 211 | function create_UIBox_notify_alert(_achievement, _type) 212 | local ret = create_UIBox_notify_alert_ref(_achievement, _type) 213 | local title = ret.nodes[1].nodes[1].nodes[2].nodes[1].nodes[1].config 214 | if _type == "Partner" then 215 | title.text = localize("k_partner") 216 | end 217 | return ret 218 | end 219 | 220 | function Card:add_partner_speech_bubble(forced_key) 221 | if not Partner_API.config.enable_speech_bubble then return end 222 | if self.children.speech_bubble then self.children.speech_bubble:remove() end 223 | local align = nil 224 | if self.T.x+self.T.w/2 > G.ROOM.T.w/2 then align = "cl" end 225 | self.config.speech_bubble_align = {align = align or "cr", offset = {x=align and -0.1 or 0.1,y=0}, parent = self} 226 | self.children.speech_bubble = UIBox{ 227 | definition = G.UIDEF.partner_speech_bubble(forced_key), 228 | config = self.config.speech_bubble_align 229 | } 230 | self.children.speech_bubble:set_role{role_type = "Minor", xy_bond = "Strong", r_bond = "Weak", major = self} 231 | self.children.speech_bubble.states.visible = false 232 | local hold_time = (G.SETTINGS.GAMESPEED*4) or 4 233 | G.E_MANAGER:add_event(Event({trigger = "after", delay = hold_time, blockable = false, blocking = false, func = function() 234 | self:remove_partner_speech_bubble() 235 | return true end})) 236 | end 237 | 238 | function G.UIDEF.partner_speech_bubble(forced_key) 239 | local text = {} 240 | localize{type = "quips", key = forced_key or "pq_1", nodes=text} 241 | local row = {} 242 | for k, v in ipairs(text) do 243 | row[#row+1] = {n=G.UIT.R, config={align = "cl"}, nodes=v} 244 | end 245 | local t = {n=G.UIT.ROOT, config = {align = "cm", minh = 1, r = 0.3, padding = 0.07, minw = 1, colour = G.C.JOKER_GREY, shadow = true}, nodes={ 246 | {n=G.UIT.C, config={align = "cm", minh = 1, r = 0.2, padding = 0.1, minw = 1, colour = G.C.WHITE}, nodes={ 247 | {n=G.UIT.C, config={align = "cm", minh = 1, r = 0.2, padding = 0.03, minw = 1, colour = G.C.WHITE}, nodes=row} 248 | }} 249 | }} 250 | return t 251 | end 252 | 253 | function Card:partner_say_stuff(n, not_first) 254 | if not Partner_API.config.enable_speech_bubble then return end 255 | self.talking = true 256 | if not not_first then 257 | G.E_MANAGER:add_event(Event({trigger = "after", delay = 0.1, func = function() 258 | if self.children.speech_bubble then self.children.speech_bubble.states.visible = true end 259 | self:partner_say_stuff(n, true) 260 | return true end})) 261 | else 262 | if n <= 0 then self.talking = false; return end 263 | play_sound("voice"..math.random(1, 11), G.SPEEDFACTOR*(math.random()*0.2+1), 0.5) 264 | self:juice_up() 265 | G.E_MANAGER:add_event(Event({trigger = "after", blockable = false, blocking = false, delay = 0.13, func = function() 266 | self:partner_say_stuff(n-1, true) 267 | return true end})) 268 | end 269 | end 270 | 271 | function Card:remove_partner_speech_bubble() 272 | if self.children.speech_bubble then self.children.speech_bubble:remove(); self.children.speech_bubble = nil end 273 | end 274 | 275 | local Card_draw_ref = Card.draw 276 | function Card:draw(layer) 277 | Card_draw_ref(self, layer) 278 | if self.children.speech_bubble then 279 | self.children.speech_bubble:draw() 280 | end 281 | end 282 | 283 | local Card_move_ref = Card.move 284 | function Card:move(dt) 285 | Card_move_ref(self, dt) 286 | if self.children.speech_bubble then 287 | local align = nil 288 | if self.T.x+self.T.w/2 > G.ROOM.T.w/2 then align = "cl" end 289 | self.children.speech_bubble:set_alignment({type = align or "cr", offset = {x=align and -0.1 or 0.1,y=0}, parent = self}) 290 | end 291 | end 292 | 293 | -- New Run Page 294 | 295 | G.FUNCS.run_setup_partners_option = function(e) 296 | G.SETTINGS.paused = true 297 | G.FUNCS.overlay_menu{ 298 | definition = create_UIBox_partners_option(), 299 | config = {no_esc = true} 300 | } 301 | end 302 | 303 | function create_UIBox_partners_option() 304 | G.GAME.viewed_partner = G.P_CENTER_POOLS["Partner"][G.PROFILES[G.SETTINGS.profile].MEMORY.partner] or G.P_CENTER_POOLS["Partner"][1] 305 | local partner_selection, partner_selection_cycle = create_partner_selection() 306 | G.partner_area = CardArea(G.ROOM.T.x, G.ROOM.T.h, G.CARD_W*46/71, G.CARD_H*58/95, {card_limit = 2, type = "title", highlight_limit = 0}) 307 | local center = G.GAME.viewed_partner 308 | local card = Card(G.partner_area.T.x+G.partner_area.T.w/2-G.CARD_W*23/71, G.partner_area.T.y+G.partner_area.T.h/2-G.CARD_H*29/95, G.CARD_W*46/71, G.CARD_H*58/95, G.P_CARDS.empty, center) 309 | local minw = 3 310 | local UI_table = G.GAME.viewed_partner:is_unlocked() and generate_card_ui(G.GAME.viewed_partner, nil, nil, "Partner") or generate_card_ui(G.GAME.viewed_partner, nil, nil, "Locked") 311 | local partner_main = {n=G.UIT.ROOT, config={align = "cm", minw = minw, minh = 2, id = G.GAME.viewed_partner.name, colour = G.C.CLEAR}, nodes={desc_from_rows(UI_table.main, true, minw-0.2)}} 312 | --card.sticker = get_joker_win_sticker(center) 313 | card.states.hover.can = false 314 | G.partner_area:emplace(card) 315 | local t = create_UIBox_generic_options({no_back = true, contents = { 316 | {n=G.UIT.R, config={align = "cm", minw = 2.5, padding = 0.15, r = 0.1, colour = G.C.L_BLACK}, nodes={ 317 | {n=G.UIT.C, config={align = "cm", padding = 0}, nodes={ 318 | partner_selection, 319 | partner_selection_cycle 320 | }}, 321 | {n=G.UIT.C, config={align = "tm", minw = 3, minh = 1, r = 0.1, colour = G.C.BLACK, padding = 0.15, emboss = 0.05}, nodes={ 322 | {n=G.UIT.R, config={align = "cm", emboss = 0.1, r = 0.1, minw = 2, minh = 0.5}, nodes={ 323 | {n=G.UIT.O, config={id = nil, func = "RUN_SETUP_check_partner_name", object = Moveable()}}, 324 | }}, 325 | {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ 326 | {n=G.UIT.O, config={id = G.GAME.viewed_partner.name, func = "RUN_SETUP_check_partner_card", object = G.partner_area}}, 327 | }}, 328 | {n=G.UIT.R, config={align = "cm", colour = G.C.WHITE, emboss = 0.1, r = 0.1}, nodes={ 329 | {n=G.UIT.O, config={id = G.GAME.viewed_partner.name, func = "RUN_SETUP_check_partner", object = UIBox{definition = partner_main, config = {offset = {x=0,y=0}}}}} 330 | }} 331 | }}, 332 | }}, 333 | {n=G.UIT.R, config={align = "cm", padding = 0}, nodes={ 334 | {n=G.UIT.C, config={minw = 2.72, minh = 0.8, r = 0.1, hover = true, button = "skip_partner", colour = G.C.FILTER, align = "cm", emboss = 0.1}, nodes={ 335 | {n=G.UIT.R, config={align = "cm"}, nodes={ 336 | {n=G.UIT.T, config={text = localize("b_partner_skip"), scale = 0.5, colour = G.C.WHITE}} 337 | }}, 338 | }}, 339 | {n=G.UIT.C, config={align = "cm", minw = 0.2}, nodes={}}, 340 | {n=G.UIT.C, config={minw = 2.72, minh = 0.8, r = 0.1, hover = true, button = "random_partner", colour = G.C.BLUE, align = "cm", emboss = 0.1}, nodes={ 341 | {n=G.UIT.R, config={align = "cm"}, nodes={ 342 | {n=G.UIT.T, config={text = localize("b_partner_random"), scale = 0.5, colour = G.C.WHITE}} 343 | }}, 344 | }}, 345 | {n=G.UIT.C, config={align = "cm", minw = 0.2}, nodes={}}, 346 | {n=G.UIT.C, config={minw = 3.33, minh = 0.8, r = 0.1, hover = true, button = "select_partner", func = "select_partner_button", align = "cm", emboss = 0.1}, nodes={ 347 | {n=G.UIT.R, config={align = "cm"}, nodes={ 348 | {n=G.UIT.T, config={text = localize("b_partner_agree"), scale = 0.5, colour = G.C.WHITE}} 349 | }}, 350 | }}, 351 | }}, 352 | }}) 353 | return t 354 | end 355 | 356 | function create_partner_selection() 357 | local partner_tables = {} 358 | G.partner_selection = {} 359 | for i = 1, 2 do 360 | local row = {n=G.UIT.R, config={colour = G.C.LIGHT}, nodes={}} 361 | for j = 1, 4 do 362 | G.partner_selection[j+(i-1)*4] = CardArea(G.ROOM.T.x, G.ROOM.T.h, G.CARD_W*46/71, G.CARD_H*58/95, {card_limit = 2, type = "title", highlight_limit = 0}) 363 | table.insert(row.nodes, {n=G.UIT.O, config={object = G.partner_selection[j+(i-1)*4]}}) 364 | end 365 | table.insert(partner_tables, row) 366 | end 367 | local partner_options = {} 368 | for i = 1, math.ceil(#G.P_CENTER_POOLS["Partner"]/(#G.partner_selection)) do 369 | table.insert(partner_options, localize("k_page").." "..tostring(i).."/"..tostring(math.ceil(#G.P_CENTER_POOLS["Partner"]/(#G.partner_selection)))) 370 | end 371 | local viewed_partner = 1 372 | for k, v in pairs(G.P_CENTER_POOLS["Partner"]) do 373 | if v.name == G.GAME.viewed_partner.name then 374 | viewed_partner = math.ceil(k/(#G.partner_selection)) 375 | break 376 | end 377 | end 378 | for i = 1, #G.partner_selection do 379 | local center = G.P_CENTER_POOLS["Partner"][i+(#G.partner_selection*(viewed_partner-1))] 380 | if not center then break end 381 | local card = Card(G.partner_selection[i].T.x+G.partner_selection[i].T.w/2-G.CARD_W*23/71, G.partner_selection[i].T.y+G.partner_selection[i].T.h/2-G.CARD_H*29/95, G.CARD_W*46/71, G.CARD_H*58/95, empty, center) 382 | card.no_ui = true; card.config.card.no_ui = true 383 | card.ability.fake_partner = true 384 | G.partner_selection[i]:emplace(card) 385 | end 386 | local t, tt = {n=G.UIT.R, config={align = "cm", r = 0.1, minh = 3.6, colour = G.C.BLACK, emboss = 0.05}, nodes=partner_tables}, 387 | {n=G.UIT.R, config={align = "cm"}, nodes={ 388 | create_option_cycle({options = partner_options, w = 2.5, cycle_shoulders = true, opt_callback = "your_selection_partner_page", current_option = viewed_partner, colour = G.C.RED, no_pips = true, focus_args = {snap_to = true, nav = "wide"}}) 389 | }} 390 | return t, tt 391 | end 392 | 393 | G.FUNCS.your_selection_partner_page = function(args) 394 | if not args or not args.cycle_config then return end 395 | for j = 1, #G.partner_selection do 396 | for i = #G.partner_selection[j].cards, 1, -1 do 397 | local c = G.partner_selection[j]:remove_card(G.partner_selection[j].cards[i]) 398 | c:remove() 399 | c = nil 400 | end 401 | end 402 | for j = 1, #G.partner_selection do 403 | local center = G.P_CENTER_POOLS["Partner"][j+(#G.partner_selection*(args.cycle_config.current_option-1))] 404 | if not center then break end 405 | local card = Card(G.partner_selection[j].T.x+G.partner_selection[j].T.w/2-G.CARD_W*23/71, G.partner_selection[j].T.y+G.partner_selection[j].T.h/2-G.CARD_H*29/95, G.CARD_W*46/71, G.CARD_H*58/95, G.P_CARDS.empty, center) 406 | card.no_ui = true; card.config.card.no_ui = true 407 | card.ability.fake_partner = true 408 | G.partner_selection[j]:emplace(card) 409 | end 410 | end 411 | 412 | G.FUNCS.RUN_SETUP_check_partner_card = function(e) 413 | if e.config.object and G.GAME.viewed_partner.name ~= e.config.id then 414 | local c = G.partner_area:remove_card(G.partner_area.cards[1]) 415 | c:remove() 416 | c = nil 417 | local center = G.GAME.viewed_partner 418 | local card = Card(G.partner_area.T.x+G.partner_area.T.w/2-G.CARD_W*23/71, G.partner_area.T.y+G.partner_area.T.h/2-G.CARD_H*29/95, G.CARD_W*46/71, G.CARD_H*58/95, G.P_CARDS.empty, center) 419 | card.states.hover.can = false 420 | G.partner_area:emplace(card) 421 | e.config.id = G.GAME.viewed_partner.name 422 | end 423 | end 424 | 425 | G.FUNCS.RUN_SETUP_check_partner_name = function(e) 426 | if e.config.object and G.GAME.viewed_partner.name ~= e.config.id then 427 | local partner_name = G.GAME.viewed_partner:is_unlocked() and localize{type = "name_text", set = "Partner", key = G.GAME.viewed_partner.key} or localize("k_locked") 428 | e.config.object:remove() 429 | e.config.object = UIBox{ 430 | definition = {n=G.UIT.ROOT, config={align = "cm", colour = G.C.CLEAR}, nodes={ 431 | {n=G.UIT.O, config={id = G.GAME.viewed_partner.name, func = "RUN_SETUP_check_partner_name", object = DynaText({string = partner_name, maxw = 4, colours = {G.C.WHITE}, shadow = true, bump = true, scale = 0.5, pop_in = 0, silent = true})}}, 432 | }}, 433 | config = {offset = {x=0,y=0}, align = "cm", parent = e} 434 | } 435 | e.config.id = G.GAME.viewed_partner.name 436 | end 437 | end 438 | 439 | G.FUNCS.RUN_SETUP_check_partner = function(e) 440 | if G.GAME.viewed_partner.name ~= e.config.id then 441 | local minw = 3 442 | local UI_table = G.GAME.viewed_partner:is_unlocked() and generate_card_ui(G.GAME.viewed_partner, nil, nil, "Partner") or generate_card_ui(G.GAME.viewed_partner, nil, nil, "Locked") 443 | local partner_main = {n=G.UIT.ROOT, config={align = "cm", minw = minw, minh = 2, id = G.GAME.viewed_partner.name, colour = G.C.CLEAR}, nodes={desc_from_rows(UI_table.main, true, minw-0.2)}} 444 | e.config.object:remove() 445 | e.config.object = UIBox{ 446 | definition = partner_main, 447 | config = {offset = {x=0,y=0}, align = "cm", parent = e} 448 | } 449 | e.config.id = G.GAME.viewed_partner.name 450 | end 451 | end 452 | 453 | G.FUNCS.skip_partner = function() 454 | G.FUNCS.exit_overlay_menu() 455 | G.GAME.skip_partner = true 456 | end 457 | 458 | G.FUNCS.random_partner = function() 459 | local center = pseudorandom_element(G.P_CENTER_POOLS["Partner"], pseudoseed(os.time())) 460 | G.GAME.viewed_partner = center 461 | for k, v in pairs(G.P_CENTER_POOLS["Partner"]) do 462 | if v == G.GAME.viewed_partner then 463 | G.PROFILES[G.SETTINGS.profile].MEMORY.partner = k 464 | end 465 | end 466 | end 467 | 468 | G.FUNCS.select_partner_button = function(e) 469 | if G.GAME.viewed_partner and G.GAME.viewed_partner:is_unlocked() then 470 | e.config.colour = G.C.GREEN 471 | e.config.button = "select_partner" 472 | else 473 | e.config.colour = G.C.UI.BACKGROUND_INACTIVE 474 | e.config.button = nil 475 | end 476 | end 477 | 478 | G.FUNCS.select_partner = function() 479 | G.FUNCS.exit_overlay_menu() 480 | G.E_MANAGER:add_event(Event({func = function() 481 | G.GAME.selected_partner = G.GAME.viewed_partner.key 482 | G.GAME.selected_partner_card = Card(G.deck.T.x+G.deck.T.w-G.CARD_W*0.6, G.deck.T.y-G.CARD_H*1.6, G.CARD_W*46/71, G.CARD_H*58/95, G.P_CARDS.empty, G.GAME.viewed_partner) 483 | G.GAME.selected_partner_card:juice_up(0.3, 0.5) 484 | local ret = G.GAME.selected_partner_card:calculate_partner_begin() 485 | if ret then SMODS.trigger_effects({{individual = ret}}, G.GAME.selected_partner_card) end 486 | return true end})) 487 | end 488 | 489 | local run_setup_option_ref = G.UIDEF.run_setup_option 490 | function G.UIDEF.run_setup_option(type) 491 | local t = run_setup_option_ref(type) 492 | if type == "New Run" then 493 | t.nodes[#t.nodes].nodes[#t.nodes[#t.nodes].nodes] = {n=G.UIT.C, config={align = "cm", minw = 2.4}, nodes={ 494 | type == "New Run" and create_toggle{col = true, label = localize("k_partner"), label_scale = 0.28, w = 0, scale = 0.7, ref_table = Partner_API.config, ref_value = "enable_partner"} or nil 495 | }} 496 | end 497 | return t 498 | end 499 | 500 | -- Galdur Compat 501 | local run_setup_option_new_model_ref = G.UIDEF.run_setup_option_new_model 502 | function G.UIDEF.run_setup_option_new_model(type) 503 | local t = run_setup_option_new_model_ref(type) 504 | t.nodes[#t.nodes].nodes[2].nodes[#t.nodes[#t.nodes].nodes[2].nodes+1] = {n=G.UIT.C, config={align = "cm", minw = 2.4}, nodes={ 505 | type == "New Run" and create_toggle{col = true, label = localize("k_partner"), label_scale = 0.28, w = 0, scale = 0.7, ref_table = Partner_API.config, ref_value = "enable_partner"} or nil 506 | }} 507 | return t 508 | end 509 | 510 | local Game_start_run_ref = Game.start_run 511 | function Game:start_run(args) 512 | Game_start_run_ref(self, args) 513 | if not G.GAME.selected_partner and not G.GAME.skip_partner and Partner_API.config.enable_partner then 514 | G.E_MANAGER:add_event(Event({func = function() 515 | G.FUNCS.run_setup_partners_option() 516 | return true end})) 517 | elseif G.GAME.selected_partner then 518 | G.E_MANAGER:add_event(Event({func = function() 519 | local center = nil 520 | for k, v in pairs(G.P_CENTER_POOLS["Partner"]) do 521 | if v.key == G.GAME.selected_partner then center = v end 522 | end 523 | G.GAME.selected_partner_card = Card(G.deck.T.x+G.deck.T.w-G.CARD_W*0.6, G.deck.T.y-G.CARD_H*1.6, G.CARD_W*46/71, G.CARD_H*58/95, G.P_CARDS.empty, center) 524 | G.GAME.selected_partner_card:juice_up(0.3, 0.5) 525 | if G.GAME.selected_partner_table then 526 | for k, v in pairs(G.GAME.selected_partner_table) do 527 | G.GAME.selected_partner_card.ability.extra[k] = v 528 | end 529 | G.GAME.selected_partner_table = nil 530 | end 531 | return true end})) 532 | end 533 | end 534 | 535 | -- Controller Page 536 | 537 | local Controller_queue_R_cursor_press_ref = Controller.queue_R_cursor_press 538 | function Controller:queue_R_cursor_press(x, y) 539 | Controller_queue_R_cursor_press_ref(self, x, y) 540 | if self.locks.frame then return end 541 | self.partner_R_cursor_queue = {x = x, y = y} 542 | end 543 | 544 | local Controller_update_ref = Controller.update 545 | function Controller:update(dt) 546 | Controller_update_ref(self, dt) 547 | if self.partner_R_cursor_queue then 548 | self:partner_R_cursor_press(self.partner_R_cursor_queue.x, self.partner_R_cursor_queue.y) 549 | self.partner_R_cursor_queue = nil 550 | end 551 | if not self.cursor_up.partner_R_handled then 552 | if self.cursor_down.partner_R_target then 553 | if (not self.cursor_down.partner_R_target.click_timeout or self.cursor_down.partner_R_target.click_timeout*G.SPEEDFACTOR > self.cursor_up.partner_R_time - self.cursor_down.partner_R_time) then 554 | if Vector_Dist(self.cursor_down.partner_R_T, self.cursor_up.partner_R_T) < G.MIN_CLICK_DIST then 555 | if self.cursor_down.partner_R_target.states.click.can then 556 | self.clicked.partner_R_target = self.cursor_down.partner_R_target 557 | self.clicked.partner_R_handled = false 558 | end 559 | end 560 | end 561 | end 562 | self.cursor_up.partner_R_handled = true 563 | end 564 | if not self.clicked.partner_R_handled then 565 | if self.clicked.partner_R_target then 566 | self.clicked.partner_R_target:partner_R_click() 567 | self.clicked.partner_R_handled = true 568 | end 569 | end 570 | end 571 | 572 | function Controller:partner_R_cursor_press(x, y) 573 | if ((self.locked) and (not G.SETTINGS.paused or G.screenwipe)) or (self.locks.frame) then return end 574 | local x = x or self.cursor_position.x 575 | local y = y or self.cursor_position.y 576 | self.cursor_down.partner_R_T = {x = x/(G.TILESCALE*G.TILESIZE), y = y/(G.TILESCALE*G.TILESIZE)} 577 | self.cursor_down.partner_R_time = G.TIMERS.TOTAL 578 | self.cursor_down.partner_R_target = nil 579 | local press_node = (self.HID.touch and self.cursor_hover.target) or self.hovering.target or self.focused.target 580 | if press_node then 581 | self.cursor_down.partner_R_target = press_node.states.click.can and press_node or press_node:can_drag() or nil 582 | end 583 | if self.cursor_down.partner_R_target == nil then 584 | self.cursor_down.partner_R_target = G.ROOM 585 | end 586 | end 587 | 588 | local love_mousereleased_ref = love.mousereleased 589 | function love.mousereleased(x, y, button) 590 | love_mousereleased_ref(x, y, button) 591 | if button == 2 then G.CONTROLLER:partner_R_cursor_release(x, y) end 592 | end 593 | 594 | local Controller_button_release_update_ref = Controller.button_release_update 595 | function Controller:button_release_update(button, dt) 596 | Controller_button_release_update_ref(self, button, dt) 597 | if not self.held_button_times[button] then return end 598 | if button == "b" then 599 | self:partner_R_cursor_release() 600 | end 601 | end 602 | 603 | function Controller:partner_R_cursor_release(x, y) 604 | if ((self.locked) and (not G.SETTINGS.paused or G.screenwipe)) or (self.locks.frame) then return end 605 | local x = x or self.cursor_position.x 606 | local y = y or self.cursor_position.y 607 | self.cursor_up.partner_R_T = {x = x/(G.TILESCALE*G.TILESIZE), y = y/(G.TILESCALE*G.TILESIZE)} 608 | self.cursor_up.partner_R_time = G.TIMERS.TOTAL 609 | self.cursor_up.partner_R_handled = false 610 | end 611 | 612 | function Node:partner_R_click() end 613 | 614 | -- Hook Page 615 | 616 | local save_run_ref = save_run 617 | function save_run() 618 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 619 | G.GAME.selected_partner_table = G.GAME.selected_partner_table or {} 620 | for k, v in pairs(G.GAME.selected_partner_card.ability.extra) do 621 | G.GAME.selected_partner_table[k] = v 622 | end 623 | end 624 | save_run_ref() 625 | end 626 | 627 | local Card_click_ref = Card.click 628 | function Card:click() 629 | Card_click_ref(self) 630 | if self.ability.set == "Partner" and self.ability.fake_partner then 631 | G.GAME.viewed_partner = self.config.center 632 | for k, v in pairs(G.P_CENTER_POOLS["Partner"]) do 633 | if v == G.GAME.viewed_partner then 634 | G.PROFILES[G.SETTINGS.profile].MEMORY.partner = k 635 | end 636 | end 637 | end 638 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability and G.GAME.selected_partner_card == self then 639 | if self.children.speech_bubble then 640 | self:remove_partner_speech_bubble() 641 | elseif not G.GAME.partner_click_deal then 642 | G.GAME.partner_click_deal = true 643 | local ret = G.GAME.selected_partner_card:calculate_partner({partner_click = true}) 644 | if ret then 645 | SMODS.trigger_effects({{individual = ret}}, G.GAME.selected_partner_card) 646 | end 647 | G.E_MANAGER:add_event(Event({func = function() 648 | G.GAME.partner_click_deal = nil 649 | return true end})) 650 | end 651 | end 652 | end 653 | 654 | function Card:partner_R_click() 655 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability and G.GAME.selected_partner_card == self then 656 | if not G.GAME.partner_R_click_deal then 657 | G.GAME.partner_R_click_deal = true 658 | local ret = G.GAME.selected_partner_card:calculate_partner({partner_R_click = true}) 659 | if ret then 660 | SMODS.trigger_effects({{individual = ret}}, G.GAME.selected_partner_card) 661 | end 662 | G.E_MANAGER:add_event(Event({func = function() 663 | G.GAME.partner_R_click_deal = nil 664 | return true end})) 665 | end 666 | end 667 | end 668 | 669 | local playing_card_joker_effects_ref = playing_card_joker_effects 670 | function playing_card_joker_effects(cards) 671 | playing_card_joker_effects_ref(cards) 672 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 673 | local ret = G.GAME.selected_partner_card:calculate_partner({playing_card_added = true, cards = cards}) 674 | if ret and ret.duplication then 675 | for k, v in ipairs(ret) do 676 | SMODS.trigger_effects({{individual = v}}, G.GAME.selected_partner_card) 677 | end 678 | elseif ret then 679 | SMODS.trigger_effects({{individual = ret}}, G.GAME.selected_partner_card) 680 | end 681 | end 682 | end 683 | 684 | -- Talisman Compat 685 | to_big = to_big or function(a) 686 | return a 687 | end 688 | 689 | to_number = to_number or function(a) 690 | return a 691 | end 692 | 693 | function Card:calculate_partner(context) 694 | if not context then return end 695 | local obj = self.config.center 696 | if self.ability.set == "Partner" and obj.calculate and type(obj.calculate) == "function" then 697 | local ret = obj:calculate(self, context) 698 | self:general_partner_speech(context) 699 | if ret then return ret end 700 | end 701 | end 702 | 703 | function Card:general_partner_speech(context) 704 | if not context or self.config.center.no_quips then return end 705 | if context.setting_blind and G.GAME.round == 1 then 706 | if self.config.center.individual_quips then 707 | G.E_MANAGER:add_event(Event({func = function() 708 | local max_quips = 0 709 | for k, v in pairs(G.localization.misc.quips) do 710 | if string.find(k, self.config.center.key) then 711 | max_quips = max_quips + 1 712 | end 713 | end 714 | self:add_partner_speech_bubble(self.config.center.key.."_"..math.random(1, max_quips)) 715 | self:partner_say_stuff(5) 716 | return true end})) 717 | else 718 | G.E_MANAGER:add_event(Event({func = function() 719 | self:add_partner_speech_bubble("pnr_"..math.random(1,6)) 720 | self:partner_say_stuff(5) 721 | return true end})) 722 | end 723 | end 724 | if context.setting_blind and context.blind.boss and G.GAME.round_resets.ante == G.GAME.win_ante then 725 | if self.config.center.individual_quips then 726 | G.E_MANAGER:add_event(Event({func = function() 727 | local max_quips = 0 728 | for k, v in pairs(G.localization.misc.quips) do 729 | if string.find(k, self.config.center.key) then 730 | max_quips = max_quips + 1 731 | end 732 | end 733 | self:add_partner_speech_bubble(self.config.center.key.."_"..math.random(1, max_quips)) 734 | self:partner_say_stuff(5) 735 | return true end})) 736 | else 737 | G.E_MANAGER:add_event(Event({func = function() 738 | self:add_partner_speech_bubble("dq_1") 739 | self:partner_say_stuff(5) 740 | return true end})) 741 | end 742 | end 743 | end 744 | 745 | function Card:calculate_partner_cash() 746 | local obj = self.config.center 747 | if self.ability.set == "Partner" and obj.calculate_cash and type(obj.calculate_cash) == "function" then 748 | local ret = obj:calculate_cash(self) 749 | if ret then return ret end 750 | end 751 | end 752 | 753 | function Card:calculate_partner_begin() 754 | local obj = self.config.center 755 | if self.ability.set == "Partner" and obj.calculate_begin and type(obj.calculate_begin) == "function" then 756 | local ret = obj:calculate_begin(self) 757 | if ret then return ret end 758 | end 759 | end 760 | 761 | local SMODS_calculate_repetitions_ref = SMODS.calculate_repetitions 762 | SMODS.calculate_repetitions = function(card, context, reps) 763 | local reps = SMODS_calculate_repetitions_ref(card, context, reps) 764 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 765 | local ret = G.GAME.selected_partner_card:calculate_partner(context) 766 | if ret and ret.duplication then 767 | for k, v in ipairs(ret) do 768 | SMODS.insert_repetitions(reps, v, card) 769 | end 770 | elseif ret then 771 | SMODS.insert_repetitions(reps, ret, card) 772 | end 773 | end 774 | return reps 775 | end 776 | 777 | local SMODS_calculate_card_areas_ref = SMODS.calculate_card_areas 778 | function SMODS.calculate_card_areas(_type, context, return_table, args) 779 | local flags = SMODS_calculate_card_areas_ref(_type, context, return_table, args) 780 | if _type == "individual" then 781 | if G.GAME.selected_partner_card and G.GAME.selected_partner_card.ability then 782 | local ret = G.GAME.selected_partner_card:calculate_partner(context) 783 | if ret and ret.duplication then 784 | for k, v in ipairs(ret) do 785 | if return_table then 786 | return_table[#return_table+1] = {individual = v} 787 | else 788 | local f = SMODS.trigger_effects({{individual = v}}, G.GAME.selected_partner_card) 789 | for k, v in pairs(f) do flags[k] = v end 790 | end 791 | end 792 | elseif ret then 793 | if return_table then 794 | return_table[#return_table+1] = {individual = ret} 795 | else 796 | local f = SMODS.trigger_effects({{individual = ret}}, G.GAME.selected_partner_card) 797 | for k, v in pairs(f) do flags[k] = v end 798 | end 799 | end 800 | end 801 | end 802 | return flags 803 | end 804 | 805 | -- Localization Page 806 | 807 | function Partner_API.process_loc_text() 808 | G.localization.descriptions.Partner = G.localization.descriptions.Partner or {} 809 | end 810 | 811 | -- Atlas Page 812 | 813 | SMODS.Atlas{ 814 | key = "modicon", 815 | px = 34, 816 | py = 34, 817 | path = "icon.png" 818 | } 819 | 820 | SMODS.Atlas{ 821 | key = "Partner", 822 | px = 46, 823 | py = 58, 824 | path = "Partners.png" 825 | } 826 | 827 | -- Register Page 828 | 829 | Partner_API.Partner{ 830 | key = "jimbo", 831 | name = "Jimbo Partner", 832 | unlocked = false, 833 | discovered = true, 834 | pos = {x = 0, y = 0}, 835 | loc_txt = {}, 836 | atlas = "Partner", 837 | config = {extra = {chips = 0, chip_mod = 3}}, 838 | link_config = {j_joker = 1}, 839 | loc_vars = function(self, info_queue, card) 840 | local link_level = self:get_link_level() 841 | local benefits = 1 842 | if link_level == 1 then benefits = 2 end 843 | return { vars = {card.ability.extra.chips, card.ability.extra.chip_mod*benefits} } 844 | end, 845 | calculate = function(self, card, context) 846 | if context.joker_main and card.ability.extra.chips >= 1 then 847 | return { 848 | message = localize{type = "variable", key = "a_chips", vars = {card.ability.extra.chips}}, 849 | chip_mod = card.ability.extra.chips, 850 | colour = G.C.CHIPS 851 | } 852 | end 853 | if context.before then 854 | local link_level = self:get_link_level() 855 | local benefits = 1 856 | if link_level == 1 then benefits = 2 end 857 | card.ability.extra.chips = card.ability.extra.chips + card.ability.extra.chip_mod*benefits 858 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_upgrade_ex"), colour = G.C.CHIPS}) 859 | end 860 | end, 861 | check_for_unlock = function(self, args) 862 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 863 | if v.key == "j_joker" then 864 | if get_joker_win_sticker(v, true) >= 8 then 865 | return true 866 | end 867 | break 868 | end 869 | end 870 | end, 871 | } 872 | 873 | Partner_API.Partner{ 874 | key = "mute", 875 | name = "Mute Partner", 876 | unlocked = false, 877 | discovered = true, 878 | pos = {x = 1, y = 0}, 879 | loc_txt = {}, 880 | atlas = "Partner", 881 | individual_quips = true, 882 | config = {extra = {repetitions = 1}}, 883 | link_config = {j_mime = 1}, 884 | loc_vars = function(self, info_queue, card) 885 | local link_level = self:get_link_level() 886 | local key = self.key 887 | local benefits = 1 888 | if link_level == 1 then 889 | key = key.."_"..link_level 890 | benefits = 2 891 | end 892 | return { key = key, vars = {card.ability.extra.repetitions*benefits} } 893 | end, 894 | calculate = function(self, card, context) 895 | if context.repetition and context.cardarea == G.hand and context.other_card == G.hand.cards[#G.hand.cards] and next(context.card_effects[1]) then 896 | local link_level = self:get_link_level() 897 | local benefits = 1 898 | if link_level == 1 then benefits = 2 end 899 | return { 900 | message = localize("k_again_ex"), 901 | repetitions = card.ability.extra.repetitions*benefits, 902 | card = card 903 | } 904 | end 905 | end, 906 | check_for_unlock = function(self, args) 907 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 908 | if v.key == "j_mime" then 909 | if get_joker_win_sticker(v, true) >= 8 then 910 | return true 911 | end 912 | break 913 | end 914 | end 915 | end, 916 | } 917 | 918 | Partner_API.Partner{ 919 | key = "unite", 920 | name = "Unite Partner", 921 | unlocked = false, 922 | discovered = true, 923 | pos = {x = 2, y = 0}, 924 | loc_txt = {}, 925 | atlas = "Partner", 926 | config = {extra = {x_mult = 1.5}}, 927 | link_config = {j_raised_fist = 1}, 928 | loc_vars = function(self, info_queue, card) 929 | local link_level = self:get_link_level() 930 | local benefits = 1 931 | if link_level == 1 then benefits = 4/3 end 932 | return { vars = {card.ability.extra.x_mult*benefits} } 933 | end, 934 | calculate = function(self, card, context) 935 | if not context.end_of_round and context.individual and context.cardarea == G.hand then 936 | local max_id = 1 937 | local raised_card = nil 938 | for i = 1, #G.hand.cards do 939 | if max_id <= G.hand.cards[i].base.id and not SMODS.has_no_rank(G.hand.cards[i]) then 940 | max_id = G.hand.cards[i].base.id 941 | raised_card = G.hand.cards[i] 942 | end 943 | end 944 | if context.other_card == raised_card then 945 | local link_level = self:get_link_level() 946 | local benefits = 1 947 | if link_level == 1 then benefits = 4/3 end 948 | if context.other_card.debuff then 949 | return { 950 | message = localize("k_debuffed"), 951 | colour = G.C.RED, 952 | card = card, 953 | } 954 | else 955 | return { 956 | x_mult = card.ability.extra.x_mult*benefits, 957 | card = card 958 | } 959 | end 960 | end 961 | end 962 | end, 963 | check_for_unlock = function(self, args) 964 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 965 | if v.key == "j_raised_fist" then 966 | if get_joker_win_sticker(v, true) >= 8 then 967 | return true 968 | end 969 | break 970 | end 971 | end 972 | end, 973 | } 974 | 975 | Partner_API.Partner{ 976 | key = "hatch", 977 | name = "Hatch Partner", 978 | unlocked = false, 979 | discovered = true, 980 | pos = {x = 3, y = 0}, 981 | loc_txt = {}, 982 | atlas = "Partner", 983 | config = {extra = {sell_cost_mod = 1}}, 984 | link_config = {j_egg = 1}, 985 | loc_vars = function(self, info_queue, card) 986 | local link_level = self:get_link_level() 987 | local benefits = 1 988 | if link_level == 1 then benefits = 2 end 989 | return { vars = {card.ability.extra.sell_cost_mod*benefits} } 990 | end, 991 | calculate = function(self, card, context) 992 | if context.end_of_round and not context.individual and not context.repetition then 993 | local link_level = self:get_link_level() 994 | local benefits = 1 995 | if link_level == 1 then benefits = 2 end 996 | for k, v in ipairs(G.jokers.cards) do 997 | v.ability.extra_value = (v.ability.extra_value or 0) + card.ability.extra.sell_cost_mod*benefits 998 | v:set_cost() 999 | end 1000 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_val_up"), colour = G.C.MONEY}) 1001 | end 1002 | end, 1003 | check_for_unlock = function(self, args) 1004 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1005 | if v.key == "j_egg" then 1006 | if get_joker_win_sticker(v, true) >= 8 then 1007 | return true 1008 | end 1009 | break 1010 | end 1011 | end 1012 | end, 1013 | } 1014 | 1015 | Partner_API.Partner{ 1016 | key = "steal", 1017 | name = "Steal Partner", 1018 | unlocked = false, 1019 | discovered = true, 1020 | pos = {x = 0, y = 1}, 1021 | loc_txt = {}, 1022 | atlas = "Partner", 1023 | config = {extra = {hands_played_mod = 2}}, 1024 | link_config = {j_burglar = 1}, 1025 | loc_vars = function(self, info_queue, card) 1026 | local link_level = self:get_link_level() 1027 | local key = self.key 1028 | if link_level == 1 then key = key.."_"..link_level end 1029 | return { key = key, vars = {card.ability.extra.hands_played_mod} } 1030 | end, 1031 | calculate = function(self, card, context) 1032 | if context.setting_blind then 1033 | local link_level = self:get_link_level() 1034 | if context.blind.boss or link_level == 1 then 1035 | if link_level == 1 then 1036 | G.E_MANAGER:add_event(Event({func = function() 1037 | ease_discard(card.ability.extra.hands_played_mod) 1038 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize{type = "variable", key = "a_discards", vars = {card.ability.extra.hands_played_mod}}}) 1039 | return true end})) 1040 | else 1041 | G.E_MANAGER:add_event(Event({func = function() 1042 | ease_hands_played(card.ability.extra.hands_played_mod) 1043 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize{type = "variable", key = "a_hands", vars = {card.ability.extra.hands_played_mod}}}) 1044 | return true end})) 1045 | end 1046 | end 1047 | end 1048 | end, 1049 | check_for_unlock = function(self, args) 1050 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1051 | if v.key == "j_burglar" then 1052 | if get_joker_win_sticker(v, true) >= 8 then 1053 | return true 1054 | end 1055 | break 1056 | end 1057 | end 1058 | end, 1059 | } 1060 | 1061 | Partner_API.Partner{ 1062 | key = "pale", 1063 | name = "Pale Partner", 1064 | unlocked = false, 1065 | discovered = true, 1066 | pos = {x = 1, y = 1}, 1067 | loc_txt = {}, 1068 | atlas = "Partner", 1069 | config = {extra = {discard_requires = 9, current_requires = 9, discard_dollars = 6}}, 1070 | link_config = {j_faceless = 1}, 1071 | loc_vars = function(self, info_queue, card) 1072 | local link_level = self:get_link_level() 1073 | local benefits = 1 1074 | if link_level == 1 then benefits = 2 end 1075 | return { vars = {card.ability.extra.discard_requires, card.ability.extra.current_requires, card.ability.extra.discard_dollars*benefits} } 1076 | end, 1077 | calculate = function(self, card, context) 1078 | if context.discard and context.other_card:is_face() then 1079 | if card.ability.extra.current_requires <= 1 then 1080 | local link_level = self:get_link_level() 1081 | local benefits = 1 1082 | if link_level == 1 then benefits = 2 end 1083 | card.ability.extra.current_requires = card.ability.extra.discard_requires 1084 | ease_dollars(card.ability.extra.discard_dollars*benefits) 1085 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("$")..card.ability.extra.discard_dollars*benefits, colour = G.C.MONEY}) 1086 | else 1087 | card.ability.extra.current_requires = card.ability.extra.current_requires - 1 1088 | end 1089 | end 1090 | end, 1091 | check_for_unlock = function(self, args) 1092 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1093 | if v.key == "j_faceless" then 1094 | if get_joker_win_sticker(v, true) >= 8 then 1095 | return true 1096 | end 1097 | break 1098 | end 1099 | end 1100 | end, 1101 | } 1102 | 1103 | Partner_API.Partner{ 1104 | key = "penalty", 1105 | name = "Penalty Partner", 1106 | unlocked = false, 1107 | discovered = true, 1108 | pos = {x = 4, y = 2}, 1109 | loc_txt = {}, 1110 | atlas = "Partner", 1111 | config = {extra = {}}, 1112 | link_config = {j_red_card = 1}, 1113 | loc_vars = function(self, info_queue, card) 1114 | local link_level = self:get_link_level() 1115 | local key = self.key 1116 | if link_level == 1 then key = key.."_"..link_level end 1117 | return { key = key, vars = {} } 1118 | end, 1119 | calculate = function(self, card, context) 1120 | if context.skipping_booster and context.booster.cost >= 1 then 1121 | local link_level = self:get_link_level() 1122 | local benefits = 2 1123 | if link_level == 1 then benefits = 1 end 1124 | ease_dollars(math.ceil(context.booster.cost/benefits)) 1125 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("$")..math.ceil(context.booster.cost/benefits), colour = G.C.MONEY}) 1126 | end 1127 | end, 1128 | check_for_unlock = function(self, args) 1129 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1130 | if v.key == "j_red_card" then 1131 | if get_joker_win_sticker(v, true) >= 8 then 1132 | return true 1133 | end 1134 | break 1135 | end 1136 | end 1137 | end, 1138 | } 1139 | 1140 | Partner_API.Partner{ 1141 | key = "fantasy", 1142 | name = "Fantasy Partner", 1143 | unlocked = false, 1144 | discovered = true, 1145 | pos = {x = 2, y = 1}, 1146 | loc_txt = {}, 1147 | atlas = "Partner", 1148 | config = {extra = {odd = 4}}, 1149 | link_config = {j_hallucination = 1}, 1150 | loc_vars = function(self, info_queue, card) 1151 | local link_level = self:get_link_level() 1152 | local benefits = 1 1153 | if link_level == 1 then benefits = 2 end 1154 | return { vars = {""..(G.GAME and G.GAME.probabilities.normal or 1), card.ability.extra.odd/benefits} } 1155 | end, 1156 | calculate = function(self, card, context) 1157 | if context.open_booster and #G.consumeables.cards + G.GAME.consumeable_buffer < G.consumeables.config.card_limit then 1158 | local link_level = self:get_link_level() 1159 | local benefits = 1 1160 | if link_level == 1 then benefits = 2 end 1161 | if pseudorandom("fan_pnr") < G.GAME.probabilities.normal/(card.ability.extra.odd/benefits) then 1162 | G.E_MANAGER:add_event(Event({func = function() 1163 | local _card = create_card("Spectral", G.consumeables, nil, nil, nil, nil, nil, "fan_pnr") 1164 | _card:add_to_deck() 1165 | G.consumeables:emplace(_card) 1166 | G.GAME.consumeable_buffer = 0 1167 | return true end})) 1168 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_plus_spectral"), colour = G.C.SECONDARY_SET.Spectral}) 1169 | end 1170 | end 1171 | end, 1172 | check_for_unlock = function(self, args) 1173 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1174 | if v.key == "j_hallucination" then 1175 | if get_joker_win_sticker(v, true) >= 8 then 1176 | return true 1177 | end 1178 | break 1179 | end 1180 | end 1181 | end, 1182 | } 1183 | 1184 | Partner_API.Partner{ 1185 | key = "oracle", 1186 | name = "Oracle Partner", 1187 | unlocked = false, 1188 | discovered = true, 1189 | pos = {x = 3, y = 1}, 1190 | loc_txt = {}, 1191 | atlas = "Partner", 1192 | config = {extra = {require_rounds = 3, current_rounds = 3}}, 1193 | link_config = {j_fortune_teller = 1}, 1194 | loc_vars = function(self, info_queue, card) 1195 | local link_level = self:get_link_level() 1196 | local key = self.key 1197 | if link_level == 1 then key = key.."_"..link_level end 1198 | info_queue[#info_queue+1] = G.P_CENTERS.p_arcana_normal_1 1199 | return { key = key, vars = {card.ability.extra.require_rounds, card.ability.extra.current_rounds} } 1200 | end, 1201 | calculate = function(self, card, context) 1202 | if context.starting_shop then 1203 | local link_level = self:get_link_level() 1204 | if card.ability.extra.current_rounds <= 1 or link_level == 1 then 1205 | card.ability.extra.current_rounds = card.ability.extra.require_rounds 1206 | G.E_MANAGER:add_event(Event({func = function() 1207 | local key = "p_arcana_normal_"..(math.random(1, 4)) 1208 | local _card = Card(G.shop_booster.T.x+G.shop_booster.T.w/2, G.shop_booster.T.y, G.CARD_W*1.27, G.CARD_H*1.27, G.P_CARDS.empty, G.P_CENTERS[key], {bypass_discovery_center = true, bypass_discovery_ui = true}) 1209 | create_shop_card_ui(_card, "Booster", G.shop_booster) 1210 | _card:start_materialize() 1211 | G.shop_booster:emplace(_card) 1212 | _card.ability.couponed = true 1213 | _card:set_cost() 1214 | return true end})) 1215 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_booster"), colour = G.C.PURPLE}) 1216 | else 1217 | card.ability.extra.current_rounds = card.ability.extra.current_rounds - 1 1218 | end 1219 | end 1220 | end, 1221 | check_for_unlock = function(self, args) 1222 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1223 | if v.key == "j_fortune_teller" then 1224 | if get_joker_win_sticker(v, true) >= 8 then 1225 | return true 1226 | end 1227 | break 1228 | end 1229 | end 1230 | end, 1231 | } 1232 | 1233 | Partner_API.Partner{ 1234 | key = "finesse", 1235 | name = "Finesse Partner", 1236 | unlocked = false, 1237 | discovered = true, 1238 | pos = {x = 2, y = 4}, 1239 | loc_txt = {}, 1240 | atlas = "Partner", 1241 | config = {extra = {hand_size = 1}}, 1242 | link_config = {j_juggler = 1}, 1243 | loc_vars = function(self, info_queue, card) 1244 | local link_level = self:get_link_level() 1245 | local key = self.key 1246 | if link_level == 1 then key = key.."_"..link_level end 1247 | return { key = key, vars = {card.ability.extra.hand_size} } 1248 | end, 1249 | calculate = function(self, card, context) 1250 | if context.setting_blind then 1251 | local link_level = self:get_link_level() 1252 | if (#G.jokers.cards + G.GAME.joker_buffer < G.jokers.config.card_limit) or link_level == 1 then 1253 | G.E_MANAGER:add_event(Event({func = function() 1254 | G.hand:change_size(card.ability.extra.hand_size) 1255 | G.GAME.round_resets.temp_handsize = (G.GAME.round_resets.temp_handsize or 0) + card.ability.extra.hand_size 1256 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize{type = "variable", key = "a_handsize", vars = {card.ability.extra.hand_size}}}) 1257 | return true end})) 1258 | end 1259 | end 1260 | end, 1261 | check_for_unlock = function(self, args) 1262 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1263 | if v.key == "j_juggler" then 1264 | if get_joker_win_sticker(v, true) >= 8 then 1265 | return true 1266 | end 1267 | break 1268 | end 1269 | end 1270 | end, 1271 | } 1272 | 1273 | Partner_API.Partner{ 1274 | key = "gilded", 1275 | name = "Gilded Partner", 1276 | unlocked = false, 1277 | discovered = true, 1278 | pos = {x = 0, y = 2}, 1279 | loc_txt = {}, 1280 | atlas = "Partner", 1281 | config = {extra = {dollars = 1, dollars_mod = 1}}, 1282 | link_config = {j_golden = 1}, 1283 | loc_vars = function(self, info_queue, card) 1284 | local link_level = self:get_link_level() 1285 | local benefits = 1 1286 | if link_level == 1 then benefits = 2 end 1287 | return { vars = {card.ability.extra.dollars, card.ability.extra.dollars_mod*benefits} } 1288 | end, 1289 | calculate = function(self, card, context) 1290 | if context.end_of_round and not context.individual and not context.repetition and G.GAME.blind.boss then 1291 | local link_level = self:get_link_level() 1292 | local benefits = 1 1293 | if link_level == 1 then benefits = 2 end 1294 | card.ability.extra.dollars = card.ability.extra.dollars + card.ability.extra.dollars_mod*benefits 1295 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_upgrade_ex"), colour = G.C.MONEY}) 1296 | end 1297 | end, 1298 | calculate_cash = function(self, card) 1299 | return card.ability.extra.dollars 1300 | end, 1301 | check_for_unlock = function(self, args) 1302 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1303 | if v.key == "j_golden" then 1304 | if get_joker_win_sticker(v, true) >= 8 then 1305 | return true 1306 | end 1307 | break 1308 | end 1309 | end 1310 | end, 1311 | } 1312 | 1313 | Partner_API.Partner{ 1314 | key = "batter", 1315 | name = "Batter Partner", 1316 | unlocked = false, 1317 | discovered = true, 1318 | pos = {x = 1, y = 2}, 1319 | loc_txt = {}, 1320 | atlas = "Partner", 1321 | config = {extra = {mult = 6}}, 1322 | link_config = {j_baseball = 1}, 1323 | loc_vars = function(self, info_queue, card) 1324 | local link_level = self:get_link_level() 1325 | local benefits = 1 1326 | if link_level == 1 then benefits = 3.5 end 1327 | return { vars = {card.ability.extra.mult*benefits} } 1328 | end, 1329 | calculate = function(self, card, context) 1330 | if context.other_joker and context.other_joker.config.center.rarity == 2 then 1331 | local link_level = self:get_link_level() 1332 | local benefits = 1 1333 | if link_level == 1 then benefits = 3.5 end 1334 | return { 1335 | mult = card.ability.extra.mult*benefits, 1336 | card = card 1337 | } 1338 | end 1339 | end, 1340 | check_for_unlock = function(self, args) 1341 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1342 | if v.key == "j_baseball" then 1343 | if get_joker_win_sticker(v, true) >= 8 then 1344 | return true 1345 | end 1346 | break 1347 | end 1348 | end 1349 | end, 1350 | } 1351 | 1352 | Partner_API.Partner{ 1353 | key = "bargain", 1354 | name = "Bargain Partner", 1355 | unlocked = false, 1356 | discovered = true, 1357 | pos = {x = 2, y = 2}, 1358 | loc_txt = {}, 1359 | atlas = "Partner", 1360 | config = {extra = {discard_dollars = 2}}, 1361 | link_config = {j_trading = 1}, 1362 | loc_vars = function(self, info_queue, card) 1363 | local link_level = self:get_link_level() 1364 | local key = self.key 1365 | if link_level == 1 then key = key.."_"..link_level end 1366 | return { key = key, vars = {card.ability.extra.discard_dollars} } 1367 | end, 1368 | calculate = function(self, card, context) 1369 | if context.discard and G.GAME.current_round.discards_left <= 1 then 1370 | local link_level = self:get_link_level() 1371 | if #context.full_hand <= 1 or (link_level == 1 and #context.full_hand <= 2) then 1372 | if context.other_card == context.full_hand[#context.full_hand] then 1373 | if link_level == 1 then 1374 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_partner_destroyed"), colour = G.C.RED}) 1375 | else 1376 | ease_dollars(-card.ability.extra.discard_dollars) 1377 | card_eval_status_text(card, "dollars", -card.ability.extra.discard_dollars) 1378 | end 1379 | end 1380 | return { remove = true } 1381 | end 1382 | end 1383 | end, 1384 | check_for_unlock = function(self, args) 1385 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1386 | if v.key == "j_trading" then 1387 | if get_joker_win_sticker(v, true) >= 8 then 1388 | return true 1389 | end 1390 | break 1391 | end 1392 | end 1393 | end, 1394 | } 1395 | 1396 | Partner_API.Partner{ 1397 | key = "memory", 1398 | name = "Memory Partner", 1399 | unlocked = false, 1400 | discovered = true, 1401 | pos = {x = 3, y = 2}, 1402 | loc_txt = {}, 1403 | atlas = "Partner", 1404 | config = {extra = {first_reroll = false}}, 1405 | link_config = {j_flash = 1}, 1406 | loc_vars = function(self, info_queue, card) 1407 | local link_level = self:get_link_level() 1408 | local key = self.key 1409 | if link_level == 1 then key = key.."_"..link_level end 1410 | info_queue[#info_queue+1] = {key = "memory_negative", set = "Other", vars = {1}} 1411 | return { key = key, vars = {} } 1412 | end, 1413 | calculate = function(self, card, context) 1414 | if context.reroll_shop and not card.ability.extra.first_reroll then 1415 | for i = 1, #G.shop_jokers.cards do 1416 | if not G.shop_jokers.cards[i].edition then 1417 | card.ability.extra.first_reroll = true 1418 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize{type = "name_text", key = "memory_negative", set = "Other"}, colour = G.C.DARK_EDITION}) 1419 | G.shop_jokers.cards[i]:set_edition({negative = true}, true) 1420 | local link_level = self:get_link_level() 1421 | if link_level == 1 then 1422 | G.shop_jokers.cards[i].ability.couponed = true 1423 | G.shop_jokers.cards[i]:set_cost() 1424 | end 1425 | break 1426 | end 1427 | end 1428 | end 1429 | if context.ending_shop and card.ability.extra.first_reroll then 1430 | card.ability.extra.first_reroll = false 1431 | end 1432 | end, 1433 | check_for_unlock = function(self, args) 1434 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1435 | if v.key == "j_flash" then 1436 | if get_joker_win_sticker(v, true) >= 8 then 1437 | return true 1438 | end 1439 | break 1440 | end 1441 | end 1442 | end, 1443 | } 1444 | 1445 | Partner_API.Partner{ 1446 | key = "stoke", 1447 | name = "Stoke Partner", 1448 | unlocked = false, 1449 | discovered = true, 1450 | pos = {x = 4, y = 1}, 1451 | loc_txt = {}, 1452 | atlas = "Partner", 1453 | config = {extra = {xmult = 1, xmult_mod = 0.5, cost = 2}}, 1454 | link_config = {j_campfire = 1}, 1455 | loc_vars = function(self, info_queue, card) 1456 | local link_level = self:get_link_level() 1457 | local benefits = 1 1458 | if link_level == 1 then benefits = 2 end 1459 | return { vars = {card.ability.extra.xmult, card.ability.extra.xmult_mod*benefits, card.ability.extra.cost} } 1460 | end, 1461 | calculate = function(self, card, context) 1462 | if context.joker_main and card.ability.extra.xmult > 1 then 1463 | return { 1464 | message = localize{type = "variable", key = "a_xmult", vars = {card.ability.extra.xmult}}, 1465 | Xmult_mod = card.ability.extra.xmult, 1466 | } 1467 | end 1468 | if context.after and card.ability.extra.xmult > 1 then 1469 | G.E_MANAGER:add_event(Event({func = function() 1470 | card.ability.extra.xmult = 1 1471 | return true end})) 1472 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_reset"), colour = G.C.RED}) 1473 | end 1474 | if context.partner_click and ((to_big(G.GAME.dollars) - to_big(G.GAME.bankrupt_at)) >= to_big(card.ability.extra.cost)) then 1475 | local link_level = self:get_link_level() 1476 | local benefits = 1 1477 | if link_level == 1 then benefits = 2 end 1478 | card.ability.extra.xmult = card.ability.extra.xmult + card.ability.extra.xmult_mod*benefits 1479 | ease_dollars(-card.ability.extra.cost) 1480 | card_eval_status_text(card, "dollars", -card.ability.extra.cost) 1481 | end 1482 | end, 1483 | check_for_unlock = function(self, args) 1484 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1485 | if v.key == "j_campfire" then 1486 | if get_joker_win_sticker(v, true) >= 8 then 1487 | return true 1488 | end 1489 | break 1490 | end 1491 | end 1492 | end, 1493 | } 1494 | 1495 | Partner_API.Partner{ 1496 | key = "verify", 1497 | name = "Verify Partner", 1498 | unlocked = false, 1499 | discovered = true, 1500 | pos = {x = 4, y = 3}, 1501 | loc_txt = {}, 1502 | atlas = "Partner", 1503 | config = {extra = {cost = 8}}, 1504 | link_config = {j_certificate = 1}, 1505 | loc_vars = function(self, info_queue, card) 1506 | local link_level = self:get_link_level() 1507 | local benefits = 1 1508 | if link_level == 1 then benefits = 2 end 1509 | return { vars = {card.ability.extra.cost/benefits} } 1510 | end, 1511 | calculate = function(self, card, context) 1512 | if context.partner_click and #G.hand.highlighted == 1 then 1513 | local link_level = self:get_link_level() 1514 | local benefits = 1 1515 | if link_level == 1 then benefits = 2 end 1516 | if (to_big(G.GAME.dollars) - to_big(G.GAME.bankrupt_at)) >= to_big(card.ability.extra.cost/benefits) then 1517 | local selected_seal = pseudorandom_element(G.P_CENTER_POOLS["Seal"], pseudoseed("pro_pnr")) 1518 | G.hand.highlighted[1]:set_seal(selected_seal.key, nil, true) 1519 | ease_dollars(-card.ability.extra.cost/benefits) 1520 | card_eval_status_text(card, "dollars", -card.ability.extra.cost/benefits) 1521 | G.E_MANAGER:add_event(Event({func = function() 1522 | G.hand:unhighlight_all() 1523 | return true end})) 1524 | end 1525 | end 1526 | end, 1527 | check_for_unlock = function(self, args) 1528 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1529 | if v.key == "j_certificate" then 1530 | if get_joker_win_sticker(v, true) >= 8 then 1531 | return true 1532 | end 1533 | break 1534 | end 1535 | end 1536 | end, 1537 | } 1538 | 1539 | Partner_API.Partner{ 1540 | key = "jump", 1541 | name = "Jump Partner", 1542 | unlocked = false, 1543 | discovered = true, 1544 | pos = {x = 0, y = 3}, 1545 | loc_txt = {}, 1546 | atlas = "Partner", 1547 | individual_quips = true, 1548 | config = {extra = {tag_mod = 1}}, 1549 | link_config = {j_throwback = 1}, 1550 | loc_vars = function(self, info_queue, card) 1551 | local link_level = self:get_link_level() 1552 | local key = self.key 1553 | local benefits = 1 1554 | if link_level == 1 then 1555 | key = key.."_"..link_level 1556 | benefits = 2 1557 | end 1558 | return { key = key, vars = {card.ability.extra.tag_mod*benefits} } 1559 | end, 1560 | calculate = function(self, card, context) 1561 | if context.skip_blind then 1562 | local link_level = self:get_link_level() 1563 | local benefits = 1 1564 | if link_level == 1 then benefits = 2 end 1565 | local tag_key = G.GAME.tags[#G.GAME.tags] and G.GAME.tags[#G.GAME.tags].key or "tag_double" 1566 | if G.GAME.tags[#G.GAME.tags].ability and G.GAME.tags[#G.GAME.tags].ability.orbital_hand then 1567 | G.orbital_hand = G.GAME.tags[#G.GAME.tags].ability.orbital_hand 1568 | end 1569 | for i = 1, benefits do add_tag(Tag(tag_key)) end 1570 | play_sound("generic1", 0.9 + math.random()*0.1, 0.8) 1571 | play_sound("holo1", 1.2 + math.random()*0.1, 0.4) 1572 | G.orbital_hand = nil 1573 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_duplicated_ex"), colour = G.C.GREEN}) 1574 | end 1575 | end, 1576 | check_for_unlock = function(self, args) 1577 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1578 | if v.key == "j_throwback" then 1579 | if get_joker_win_sticker(v, true) >= 8 then 1580 | return true 1581 | end 1582 | break 1583 | end 1584 | end 1585 | end, 1586 | } 1587 | 1588 | Partner_API.Partner{ 1589 | key = "vote", 1590 | name = "Vote Partner", 1591 | unlocked = false, 1592 | discovered = true, 1593 | pos = {x = 1, y = 3}, 1594 | loc_txt = {}, 1595 | atlas = "Partner", 1596 | config = {extra = {repetitions = 1}}, 1597 | link_config = {j_hanging_chad = 1}, 1598 | loc_vars = function(self, info_queue, card) 1599 | local link_level = self:get_link_level() 1600 | local key = self.key 1601 | local benefits = 1 1602 | if link_level == 1 then 1603 | key = key.."_"..link_level 1604 | benefits = 2 1605 | end 1606 | return { key = key, vars = {card.ability.extra.repetitions*benefits} } 1607 | end, 1608 | calculate = function(self, card, context) 1609 | if context.repetition and context.cardarea == G.play and context.other_card == context.scoring_hand[1] then 1610 | local link_level = self:get_link_level() 1611 | local benefits = 1 1612 | if link_level == 1 then benefits = 2 end 1613 | return { 1614 | message = localize("k_again_ex"), 1615 | repetitions = card.ability.extra.repetitions*benefits, 1616 | card = card 1617 | } 1618 | end 1619 | end, 1620 | check_for_unlock = function(self, args) 1621 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1622 | if v.key == "j_hanging_chad" then 1623 | if get_joker_win_sticker(v, true) >= 8 then 1624 | return true 1625 | end 1626 | break 1627 | end 1628 | end 1629 | end, 1630 | } 1631 | 1632 | Partner_API.Partner{ 1633 | key = "bleed", 1634 | name = "Bleed Partner", 1635 | unlocked = false, 1636 | discovered = true, 1637 | pos = {x = 2, y = 3}, 1638 | loc_txt = {}, 1639 | atlas = "Partner", 1640 | config = {extra = {xmult = 1.5}}, 1641 | link_config = {j_bloodstone = 1}, 1642 | loc_vars = function(self, info_queue, card) 1643 | local link_level = self:get_link_level() 1644 | local benefits = 1 1645 | if link_level == 1 then benefits = 2 end 1646 | return { vars = {card.ability.extra.xmult*benefits} } 1647 | end, 1648 | calculate = function(self, card, context) 1649 | if context.individual and context.cardarea == G.play then 1650 | local first_heart = nil 1651 | for i = 1, #context.scoring_hand do 1652 | if context.scoring_hand[i]:is_suit("Hearts") then 1653 | first_heart = context.scoring_hand[i] 1654 | break 1655 | end 1656 | end 1657 | if context.other_card == first_heart then 1658 | local link_level = self:get_link_level() 1659 | local benefits = 1 1660 | if link_level == 1 then benefits = 2 end 1661 | return { 1662 | x_mult = card.ability.extra.xmult*benefits, 1663 | colour = G.C.RED, 1664 | card = card 1665 | } 1666 | end 1667 | end 1668 | end, 1669 | check_for_unlock = function(self, args) 1670 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1671 | if v.key == "j_bloodstone" then 1672 | if get_joker_win_sticker(v, true) >= 8 then 1673 | return true 1674 | end 1675 | break 1676 | end 1677 | end 1678 | end, 1679 | } 1680 | 1681 | Partner_API.Partner{ 1682 | key = "andrew", 1683 | name = "Andrew Partner", 1684 | unlocked = false, 1685 | discovered = true, 1686 | pos = {x = 4, y = 0}, 1687 | loc_txt = {}, 1688 | atlas = "Partner", 1689 | config = {extra = {cost = 2, discard_mod = 1}}, 1690 | link_config = {j_merry_andy = 1}, 1691 | loc_vars = function(self, info_queue, card) 1692 | local link_level = self:get_link_level() 1693 | local benefits = 1 1694 | if link_level == 1 then benefits = 2 end 1695 | return { vars = {card.ability.extra.cost/benefits, card.ability.extra.discard_mod} } 1696 | end, 1697 | calculate = function(self, card, context) 1698 | if context.partner_click and G.STATE == G.STATES.SELECTING_HAND then 1699 | local link_level = self:get_link_level() 1700 | local benefits = 1 1701 | if link_level == 1 then benefits = 2 end 1702 | if (to_big(G.GAME.dollars) - to_big(G.GAME.bankrupt_at)) >= to_big(card.ability.extra.cost/benefits) then 1703 | G.E_MANAGER:add_event(Event({func = function() 1704 | ease_discard(card.ability.extra.discard_mod) 1705 | ease_dollars(-card.ability.extra.cost/benefits) 1706 | card_eval_status_text(card, "dollars", -card.ability.extra.cost/benefits) 1707 | return true end})) 1708 | end 1709 | end 1710 | end, 1711 | check_for_unlock = function(self, args) 1712 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1713 | if v.key == "j_merry_andy" then 1714 | if get_joker_win_sticker(v, true) >= 8 then 1715 | return true 1716 | end 1717 | break 1718 | end 1719 | end 1720 | end, 1721 | } 1722 | 1723 | Partner_API.Partner{ 1724 | key = "thrill", 1725 | name = "Thrill Partner", 1726 | unlocked = false, 1727 | discovered = true, 1728 | pos = {x = 1, y = 4}, 1729 | loc_txt = {}, 1730 | atlas = "Partner", 1731 | config = {extra = {chips = 50}}, 1732 | link_config = {j_stuntman = 1}, 1733 | loc_vars = function(self, info_queue, card) 1734 | local link_level = self:get_link_level() 1735 | local benefits = 1 1736 | if link_level == 1 then benefits = 4 end 1737 | return { vars = {card.ability.extra.chips*benefits} } 1738 | end, 1739 | calculate = function(self, card, context) 1740 | if context.joker_main and card.ability.extra.chips >= 1 then 1741 | local link_level = self:get_link_level() 1742 | local benefits = 1 1743 | if link_level == 1 then benefits = 4 end 1744 | return { 1745 | message = localize{type = "variable", key = "a_chips", vars = {card.ability.extra.chips*benefits}}, 1746 | chip_mod = card.ability.extra.chips*benefits, 1747 | colour = G.C.CHIPS 1748 | } 1749 | end 1750 | end, 1751 | check_for_unlock = function(self, args) 1752 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1753 | if v.key == "j_stuntman" then 1754 | if get_joker_win_sticker(v, true) >= 8 then 1755 | return true 1756 | end 1757 | break 1758 | end 1759 | end 1760 | end, 1761 | } 1762 | 1763 | Partner_API.Partner{ 1764 | key = "napkin", 1765 | name = "Napkin Partner", 1766 | unlocked = false, 1767 | discovered = true, 1768 | pos = {x = 4, y = 4}, 1769 | loc_txt = {}, 1770 | atlas = "Partner", 1771 | config = {extra = {joker_slot = 1}}, 1772 | link_config = {j_brainstorm = 1}, 1773 | loc_vars = function(self, info_queue, card) 1774 | local link_level = self:get_link_level() 1775 | local key = self.key 1776 | if link_level == 1 then key = key.."_"..link_level end 1777 | return { key = key, vars = {card.ability.extra.joker_slot} } 1778 | end, 1779 | calculate = function(self, card, context) 1780 | local link_level = self:get_link_level() 1781 | if link_level == 1 then 1782 | local _card = G.jokers and G.jokers.cards[1] 1783 | if _card and not context.no_blueprint then 1784 | context.blueprint, context.blueprint_card = 0, card 1785 | local _card_ret = _card:calculate_joker(context) 1786 | context.blueprint, context.blueprint_card = nil, nil 1787 | if _card_ret and _card_ret.duplication then 1788 | for k, v in ipairs(_card_ret) do 1789 | v.card = card 1790 | v.colour = G.C.RED 1791 | end 1792 | return _card_ret 1793 | elseif _card_ret then 1794 | _card_ret.card = card 1795 | _card_ret.colour = G.C.RED 1796 | return _card_ret 1797 | end 1798 | end 1799 | end 1800 | end, 1801 | calculate_begin = function(self, card) 1802 | if G.jokers then G.jokers.config.card_limit = G.jokers.config.card_limit + 1 end 1803 | end, 1804 | check_for_unlock = function(self, args) 1805 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1806 | if v.key == "j_stuntman" then 1807 | if get_joker_win_sticker(v, true) >= 8 then 1808 | return true 1809 | end 1810 | break 1811 | end 1812 | end 1813 | end, 1814 | } 1815 | 1816 | Partner_API.Partner{ 1817 | key = "valid", 1818 | name = "Valid Partner", 1819 | unlocked = false, 1820 | discovered = true, 1821 | pos = {x = 3, y = 4}, 1822 | loc_txt = {}, 1823 | atlas = "Partner", 1824 | config = {extra = {first_enhance = false}}, 1825 | link_config = {j_drivers_license = 1}, 1826 | loc_vars = function(self, info_queue, card) 1827 | local link_level = self:get_link_level() 1828 | local key = self.key 1829 | if link_level == 1 then key = key.."_"..link_level end 1830 | return { key = key, vars = {} } 1831 | end, 1832 | calculate = function(self, card, context) 1833 | if context.before then 1834 | local link_level = self:get_link_level() 1835 | if not card.ability.extra.first_enhance or link_level == 1 then 1836 | for i = 1, #context.scoring_hand do 1837 | if context.scoring_hand[i].config.center == G.P_CENTERS.c_base and not context.scoring_hand[i].debuff then 1838 | card.ability.extra.first_enhance = true 1839 | context.scoring_hand[i]:set_ability(pseudorandom_element(G.P_CENTER_POOLS["Enhanced"], pseudoseed("per_pnr")), nil, true) 1840 | G.E_MANAGER:add_event(Event({func = function() 1841 | context.scoring_hand[i]:juice_up() 1842 | return true end})) 1843 | return { 1844 | message = localize("k_partner_enhanced"), 1845 | colour = G.C.SECONDARY_SET.Enhanced, 1846 | card = card 1847 | } 1848 | end 1849 | end 1850 | end 1851 | end 1852 | if context.setting_blind and card.ability.extra.first_enhance then 1853 | card.ability.extra.first_enhance = false 1854 | end 1855 | end, 1856 | check_for_unlock = function(self, args) 1857 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1858 | if v.key == "j_drivers_license" then 1859 | if get_joker_win_sticker(v, true) >= 8 then 1860 | return true 1861 | end 1862 | break 1863 | end 1864 | end 1865 | end, 1866 | } 1867 | 1868 | Partner_API.Partner{ 1869 | key = "blaze", 1870 | name = "Blaze Partner", 1871 | unlocked = false, 1872 | discovered = true, 1873 | pos = {x = 3, y = 3}, 1874 | loc_txt = {}, 1875 | atlas = "Partner", 1876 | config = {extra = {odd = 4, upgrade_mod = 1}}, 1877 | link_config = {j_burnt = 1}, 1878 | loc_vars = function(self, info_queue, card) 1879 | local link_level = self:get_link_level() 1880 | local benefits = 1 1881 | if link_level == 1 then benefits = 2 end 1882 | return { vars = {""..(G.GAME and G.GAME.probabilities.normal or 1), card.ability.extra.odd/benefits} } 1883 | end, 1884 | calculate = function(self, card, context) 1885 | if context.pre_discard then 1886 | local link_level = self:get_link_level() 1887 | local benefits = 1 1888 | if link_level == 1 then benefits = 2 end 1889 | if pseudorandom("bla_pnr") < G.GAME.probabilities.normal/(card.ability.extra.odd/benefits) then 1890 | local text, disp_text = G.FUNCS.get_poker_hand_info(context.full_hand) 1891 | card_eval_status_text(card, "extra", nil, nil, nil, {message = localize("k_upgrade_ex")}) 1892 | update_hand_text({sound = "button", volume = 0.7, pitch = 0.8, delay = 0.3}, {handname = localize(text, "poker_hands"), chips = G.GAME.hands[text].chips, mult = G.GAME.hands[text].mult, level = G.GAME.hands[text].level}) 1893 | level_up_hand(card, text, nil, card.ability.extra.upgrade_mod) 1894 | update_hand_text({sound = "button", volume = 0.7, pitch = 1.1, delay = 0}, {mult = 0, chips = 0, handname = "", level = ""}) 1895 | end 1896 | end 1897 | end, 1898 | check_for_unlock = function(self, args) 1899 | for _, v in pairs(G.P_CENTER_POOLS["Joker"]) do 1900 | if v.key == "j_burnt" then 1901 | if get_joker_win_sticker(v, true) >= 8 then 1902 | return true 1903 | end 1904 | break 1905 | end 1906 | end 1907 | end, 1908 | } 1909 | --------------------------------------------------------------------------------