Compare commits

...

10 commits

Author SHA1 Message Date
Andres Gomez, Thomas (ITDV RL)
56f95094c6 Bump version to 0.13.5 2025-02-25 20:49:26 +01:00
Andres Gomez, Thomas (ITDV RL)
e776b7d870 Fix scrollable content on item dialog. 2025-02-12 14:43:51 +01:00
Andres Gomez, Thomas (ITDV RL)
381f9e7204 Bump version to 0.13.4 2025-02-12 13:23:39 +01:00
Andres Gomez, Thomas (ITDV RL)
8f45fe7a77 Fix inventory recomposition 2025-02-12 13:16:01 +01:00
Andres Gomez, Thomas (ITDV RL)
c11dc62a18 Update image cache + add support for Champion alteration 2025-02-12 11:31:57 +01:00
Andres Gomez, Thomas (ITDV RL)
6d77c94611 Bump version to 0.13.3 2025-01-21 10:39:59 +01:00
Andres Gomez, Thomas (ITDV RL)
42d865c4e3 Fix gamemaster roll sharing. 2025-01-21 10:35:27 +01:00
Andres Gomez, Thomas (ITDV RL)
86b8de9dcd Bump version to 1.13.2 2025-01-20 14:49:11 +01:00
Andres Gomez, Thomas (ITDV RL)
5fdf262bde Nam integration into the app and some small releated fixes. 2025-01-17 10:48:34 +01:00
Andres Gomez, Thomas (ITDV RL)
d5736c99ee Bump version to 0.13.1 2024-09-06 13:35:41 +02:00
80 changed files with 134 additions and 174 deletions

View file

@ -34,7 +34,7 @@ android {
applicationId = "com.pixelized.rplexicon"
minSdk = 26
targetSdk = 34
versionName = "0.13.0"
versionName = "0.13.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View file

@ -10,7 +10,6 @@ import com.pixelized.rplexicon.data.repository.character.ActionRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.data.repository.character.EquipmentRepository
import com.pixelized.rplexicon.data.repository.character.ItemsRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.data.repository.character.SpellRepository
@ -44,7 +43,6 @@ class LauncherViewModel @Inject constructor(
skillRepository: SkillRepository,
descriptionRepository: DescriptionRepository,
itemsRepository: ItemsRepository,
equipmentRepository: EquipmentRepository,
removeConRepository: RemoteConfigRepository // Unused but injected to initialize it.
) : ViewModel() {
@ -112,14 +110,6 @@ class LauncherViewModel @Inject constructor(
_error.emit(FetchErrorUio.Structure(type = Type.INVENTORY))
}
}
val equipment = async {
try {
equipmentRepository.fetchEquipment()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.EQUIPMENT))
}
}
awaitAll(characterSheet)
val alteration = async {
@ -156,7 +146,7 @@ class LauncherViewModel @Inject constructor(
}
awaitAll(order, lexicon, location, quest)
awaitAll(description, inventory, equipment, alteration, action, spell, skill)
awaitAll(description, inventory, alteration, action, spell, skill)
withContext(Dispatchers.Main) {
isLoading = false

View file

@ -28,6 +28,7 @@ import com.pixelized.rplexicon.utilitary.extentions.local.emphasis
import com.pixelized.rplexicon.utilitary.extentions.local.fail
import com.pixelized.rplexicon.utilitary.extentions.local.isBrutalCritical
import com.pixelized.rplexicon.utilitary.extentions.local.isCritical
import com.pixelized.rplexicon.utilitary.extentions.local.isImprovedCritical
import com.pixelized.rplexicon.utilitary.extentions.local.isSavageAttacks
import com.pixelized.rplexicon.utilitary.extentions.local.primary
import com.pixelized.rplexicon.utilitary.extentions.local.secondary
@ -738,7 +739,7 @@ class DiceThrowUseCase @Inject constructor(
val emphasis = status[ability].emphasis
// compute the amount of main dice to throw.
val amount = if (status.isCritical) {
val amount = if (status[ability].isCritical) {
diceThrow?.dice?.count?.times(2)?.let {
if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isSavageAttacks) it.plus(
1
@ -774,9 +775,17 @@ class DiceThrowUseCase @Inject constructor(
// check for flat dice bonus (ex: healing potion 2d4 + 2)
val relatedFlatBonus = diceThrow.flatBonus(name = action)
// compute the final roll result
// if the roll can be a critical roll check if the roll is one.
val isCriticalSuccess = when {
canMakeCriticalRoll && result.value == 20 -> true
canMakeCriticalRoll && result.value == 19 && status.isImprovedCritical -> true
else -> false
}
// compute the roll result. by default display a sum of everything except on a critical
val rollResult = when {
canMakeCriticalRoll && result.value == 20 -> "20"
isCriticalSuccess && result.value == 20 -> "20"
isCriticalSuccess && result.value == 19 -> "19"
canMakeCriticalRoll && result.value == 1 -> "1"
else -> "${allValue.sum()}"
}
@ -785,7 +794,7 @@ class DiceThrowUseCase @Inject constructor(
return DiceThrowResult(
dice = RollDiceUio(
icon = diceThrow?.dice?.icon ?: R.drawable.ic_d20_24,
isCriticalSuccess = canMakeCriticalRoll && result.value == 20,
isCriticalSuccess = isCriticalSuccess,
isCriticalFailure = canMakeCriticalRoll && result.value == 1,
result = rollResult,
),
@ -794,7 +803,7 @@ class DiceThrowUseCase @Inject constructor(
title = titleString.uppercase(),
highlight = action.uppercase(),
face = diceThrow?.dice?.faces ?: 20,
isCriticalSuccess = canMakeCriticalRoll && result.value == 20,
isCriticalSuccess = isCriticalSuccess,
isCriticalFailure = canMakeCriticalRoll && result.value == 1,
roll = allValue.toLabel(),
result = rollResult,
@ -1070,7 +1079,7 @@ class DiceThrowUseCase @Inject constructor(
return this?.flatMap { status ->
status.dices.map { dice ->
val localRoll = roll(
amount = if (this@ThrowScope.status.isCritical) dice.count * 2 else dice.count,
amount = if (isCritical) dice.count * 2 else dice.count,
faces = dice.faces,
advantage = dice.advantage,
disadvantage = dice.disadvantage,

View file

@ -5,6 +5,7 @@ import androidx.annotation.StringRes
import com.pixelized.rplexicon.R
data class CharacterSheet(
val active: Boolean,
val name: String,
val race: String?,
val proficiency: Int, // Bonus de maîtrise
@ -22,7 +23,7 @@ data class CharacterSheet(
val spell9: Int?, // level 9 spell slot
val dC: Int?, // offensive saving throw.
val armorClass: Int, // Classe d'armure
val speed: Int, // Vitesse
val speed: Float, // Vitesse
val strength: Int, // Force
val dexterity: Int, // Dextérité
val constitution: Int, // Constitution

View file

@ -33,6 +33,7 @@ class CharacterSheetParser @Inject constructor(
val name = item.parse(column = NAME)
if (name != null) {
CharacterSheet(
active = item.parseBool(column = ACTIVE) ?: false,
name = name,
race = item.parse(column = RACE),
proficiency = item.parseInt(column = MASTERY) ?: 2,
@ -50,7 +51,7 @@ class CharacterSheetParser @Inject constructor(
spell9 = item.parseInt(column = SPELL_LEVEL_9),
dC = item.parseInt(column = DD_SAVE_THROW),
armorClass = item.parseInt(column = ARMOR_CLASS) ?: 10,
speed = item.parseInt(column = SPEED) ?: 10,
speed = item.parseFloat(column = SPEED) ?: 10f,
strength = item.parseInt(column = STRENGTH) ?: 10,
dexterity = item.parseInt(column = DEXTERITY) ?: 10,
constitution = item.parseInt(column = CONSTITUTION) ?: 10,
@ -103,6 +104,7 @@ class CharacterSheetParser @Inject constructor(
}
companion object {
private val ACTIVE = column("Actif")
private val NAME = column("Nom")
private val RACE = column("Race")
private val LEVEL = column("Niveau")
@ -157,6 +159,7 @@ class CharacterSheetParser @Inject constructor(
private val ROWS
get() = listOf(
ACTIVE,
NAME,
RACE,
LEVEL,

View file

@ -1,71 +0,0 @@
package com.pixelized.rplexicon.data.parser.inventory
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.data.model.Equipment
import com.pixelized.rplexicon.data.parser.parserScope
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import javax.inject.Inject
class EquipmentParser @Inject constructor() {
@Throws(IncompatibleSheetStructure::class)
fun parse(data: ValueRange): Map<String, Equipment> = parserScope {
lateinit var characters: List<String>
val equipments = hashMapOf<String, Equipment.Builder>()
data.forEachRowIndexed { index, row ->
when (index) {
// ignore the first column
0 -> characters = row.drop(1).map { it.toString() }
else -> row.drop(1).forEachIndexed { column, item ->
val character = characters[column]
equipments.getEquipment(name = character).let {
when (row[0]) {
BACKGROUND -> it.background = item.toItem()
HEAD -> it.head = item.toItem()
FACE -> it.face = item.toItem()
NECK -> it.neck = item.toItem()
SHOULDER -> it.shoulder = item.toItem()
BODY -> it.body = item.toItem()
CHEST -> it.chest = item.toItem()
ARM -> it.arm = item.toItem()
HAND -> it.hand = item.toItem()
RING_1 -> it.ring1 = item.toItem()
RING_2 -> it.ring2 = item.toItem()
WAIST -> it.waist = item.toItem()
FOOT -> it.foot = item.toItem()
MAIN_HAND -> it.mainHand = item.toItem()
OFF_HAND -> it.offHand = item.toItem()
}
}
}
}
}
return@parserScope characters.associateWith {
equipments.getEquipment(name = it).build()
}
}
private fun HashMap<String, Equipment.Builder>.getEquipment(name: String) =
this.getOrPut(name) { Equipment.Builder() }
companion object {
private const val BACKGROUND = "Silhouette"
private const val HEAD = "Tête"
private const val FACE = "Visage"
private const val NECK = "Gorge"
private const val SHOULDER = "Épaules"
private const val BODY = "Corp"
private const val CHEST = "Torse"
private const val ARM = "Bras"
private const val HAND = "Mains"
private const val RING_1 = "Anneau 1"
private const val RING_2 = "Anneau 2"
private const val WAIST = "Taille"
private const val FOOT = "Pieds"
private const val MAIN_HAND = "Main droite"
private const val OFF_HAND = "Main gauche"
}
}

View file

@ -1,36 +0,0 @@
package com.pixelized.rplexicon.data.repository.character
import com.pixelized.rplexicon.data.model.Equipment
import com.pixelized.rplexicon.data.parser.inventory.EquipmentParser
import com.pixelized.rplexicon.data.repository.CharacterBinder
import com.pixelized.rplexicon.data.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.Update
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class EquipmentRepository @Inject constructor(
private val googleRepository: GoogleSheetServiceRepository,
private val equipmentParser: EquipmentParser,
) {
private val _data = MutableStateFlow<Map<String, Equipment>>(emptyMap())
val data: StateFlow<Map<String, Equipment>> get() = _data
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun find(name: String?): Equipment? = _data.value[name]
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchEquipment() {
googleRepository.fetch { sheet ->
val request = sheet.get(CharacterBinder.ID, CharacterBinder.EQUIPMENT)
val data = equipmentParser.parse(data = request.execute())
_data.tryEmit(data)
lastSuccessFullUpdate = Update.currentTime()
}
}
}

View file

@ -320,7 +320,10 @@ private fun rememberPortrait(): List<Int> = rememberSaveable {
listOf(
R.drawable.im_brulkhai,
R.drawable.im_leandre,
R.drawable.im_nelia,
listOf(
R.drawable.im_nelia,
R.drawable.im_nam,
).shuffled().first(),
R.drawable.im_tigrane,
R.drawable.im_unathana,
).shuffled()

View file

@ -105,10 +105,7 @@ enum class CharacterTabUio(@StringRes val label: Int) {
Proficiency(R.string.character_sheet_tab_proficiency),
}
@OptIn(
ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class,
)
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun CharacterSheetScreen(
viewModel: CharacterSheetViewModel = hiltViewModel(),
@ -490,7 +487,6 @@ private fun CharacterScreenPreview(
}
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
private fun BackHandler(
sheetState: ModalBottomSheetState,

View file

@ -10,7 +10,6 @@ import com.pixelized.rplexicon.data.repository.character.ActionRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.data.repository.character.EquipmentRepository
import com.pixelized.rplexicon.data.repository.character.ItemsRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.data.repository.character.SpellRepository
@ -33,7 +32,6 @@ class CharacterSheetViewModel @Inject constructor(
private val descriptionRepository: DescriptionRepository,
private val alterationRepository: AlterationRepository,
private val itemsRepository: ItemsRepository,
private val equipmentRepository: EquipmentRepository,
private val actionRepository: ActionRepository,
private val spellRepository: SpellRepository,
private val skillRepository: SkillRepository,
@ -93,16 +91,6 @@ class CharacterSheetViewModel @Inject constructor(
}
}
}
val equipment = async {
if (force || equipmentRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
equipmentRepository.fetchEquipment()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.EQUIPMENT))
}
}
}
awaitAll(characters)
val alterations = async {
if (force || alterationRepository.lastSuccessFullUpdate.shouldUpdate()) {
@ -144,7 +132,7 @@ class CharacterSheetViewModel @Inject constructor(
}
}
}
awaitAll(description, alterations, inventory, equipment, actions, spells, skills)
awaitAll(description, alterations, inventory, actions, spells, skills)
_isLoading.value = false
}

View file

@ -281,7 +281,7 @@ class CharacterSheetUioFactory @Inject constructor() {
),
speed = LabelPointUio(
label = R.string.character_sheet_title_speed,
value = "${max(sheet.speed + status[Property.SPEED].sum, 0)}m",
value = "${max(sheet.speed + status[Property.SPEED].sum.toFloat(), 0f)}m",
),
masteries = MasteriesUio(
martial = sheet.martial,

View file

@ -25,6 +25,9 @@ class SpellUioFactory @Inject constructor(
val modifier = diceThrow.modifier.sumOf {
when (it) {
Property.PROFICIENCY -> characterSheet.proficiency
Property.STRENGTH -> characterSheet.strength.modifier
Property.DEXTERITY -> characterSheet.dexterity.modifier
Property.CONSTITUTION -> characterSheet.constitution.modifier
Property.INTELLIGENCE -> characterSheet.intelligence.modifier
Property.WISDOM -> characterSheet.wisdom.modifier
Property.CHARISMA -> characterSheet.charisma.modifier

View file

@ -170,7 +170,6 @@ fun InventoryPage(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun InventoryContent(
modifier: Modifier = Modifier,
@ -248,7 +247,6 @@ private fun InventoryContent(
when (item) {
is String -> CategoryHeader(
modifier = Modifier
.animateItem()
.fillMaxWidth()
.padding(
start = 16.dp,

View file

@ -1,15 +1,12 @@
package com.pixelized.rplexicon.ui.screens.character.pages.inventory.composable.draggable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun LazyGridItemScope.DraggableInventoryElement(
fun DraggableInventoryElement(
modifier: Modifier = Modifier,
dragDropState: InventoryDragDropState,
index: Int,
@ -18,7 +15,6 @@ fun LazyGridItemScope.DraggableInventoryElement(
) {
Box(
modifier = Modifier
.animateItem()
.graphicsLayer {
dragDropState.draggedItemElement?.let { element ->
this.alpha = when {

View file

@ -24,11 +24,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@ -183,7 +185,9 @@ private fun ItemDetailContent(
)
}
Column(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.verticalScroll(state = rememberScrollState()),
) {
Text(
modifier = Modifier.padding(

View file

@ -36,7 +36,9 @@ import com.pixelized.rplexicon.utilitary.extentions.toLabel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -146,7 +148,7 @@ class SummaryFactory @Inject constructor(
val data = DataStruct()
characterSheetRepository.data
.combine(activeAlterationRepository.getActiveAlterations()) { sheets, alterations ->
data.sheets = sheets
data.sheets = sheets.filter { entry -> entry.value.active }
data.alterations = alterations
}
.combine(firebaseRepository.getCharacters()) { _, fire ->
@ -216,7 +218,7 @@ class SummaryFactory @Inject constructor(
label = (dexterity.modifier + status[Property.INITIATIVE].sum).toLabel()
)
val speed = label(
label = "${max(sheet.speed + status[Property.SPEED].sum, 0)}m",
label = "${max(sheet.speed + status[Property.SPEED].sum.toFloat(), 0f)}m",
)
val inspiration = proficiency(
label = null,
@ -653,7 +655,7 @@ class SummaryFactory @Inject constructor(
val data = HeaderStatStruct()
characterSheetRepository.data
.combine(throwRepository.getThrows()) { sheets, fire ->
data.sheets = sheets
data.sheets = sheets.filter { entry -> entry.value.active }
data.fires = fire.details
}
.collect {

View file

@ -224,7 +224,65 @@ object ImageCache {
"https://bg3.wiki/w/images/f/f7/Magic_Initiate_Bard_Icon.webp" to R.drawable.icbg_magic_initiate_bard,
"https://bg3.wiki/w/images/f/f9/Mage_Hand_Icon.webp" to R.drawable.icbg_mage_hand,
"https://bg3.wiki/w/images/f/fb/Camp_Rich_B_Purple_Unfaded.png" to R.drawable.icbg_camp_rich_b_purple_unfaded,
"https://bg3.wiki/w/images/b/ba/Rakish_Audacity_Icon.webp" to R.drawable.icbg_rakish_audacity,
"https://bg3.wiki/w/images/4/4d/Action_Surge_Icon.webp" to R.drawable.icbg_action_surge_icon,
"https://bg3.wiki/w/images/a/a6/Lever_Handle_Unfaded_Icon.png" to R.drawable.icbg_lever_handle_unfaded_icon,
"https://bg3.wiki/w/images/a/a5/Pouch_A_Unfaded_Icon.webp" to R.drawable.icbg_pouch_a_unfaded_icon,
"https://bg3.wiki/w/images/8/8f/Knife_A_Unfaded_Icon.png" to R.drawable.icbg_knife_a_unfaded_icon,
"https://bg3.wiki/w/images/7/78/Studded_Leather_Unfaded_Icon.png" to R.drawable.icbg_studded_leather_unfaded_icon,
"https://bg3.wiki/w/images/3/34/Book_Parchment_A_Unfaded.png" to R.drawable.icbg_book_parchment_a_unfaded,
"https://bg3.wiki/w/images/2/21/Book_Note_H_Unfaded.png" to R.drawable.icbg_book_note_h_unfaded,
"https://bg3.wiki/w/images/7/7b/Book_Grimoire_Unfaded_Icon.png" to R.drawable.icbg_book_grimoire_unfaded_icon,
"https://bg3.wiki/w/images/0/02/Gnome_Cunning_Icon.webp" to R.drawable.icbg_gnome_cunning_icon,
"https://bg3.wiki/w/images/d/d2/Training_in_War_and_Song_Icon.webp" to R.drawable.icbg_training_in_war_and_song_icon,
"https://bg3.wiki/w/images/e/ed/Arcane_Recovery_Icon.webp" to R.drawable.icbg_arcane_recovery_icon,
"https://bg3.wiki/w/images/8/80/Bladesong_Icon.webp" to R.drawable.icbg_bladesong_icon,
"https://bg3.wiki/w/images/f/fd/Booming_Blade_Icon.webp" to R.drawable.icbg_booming_blade_icon,
"https://bg3.wiki/w/images/2/23/Fire_Bolt_Icon.webp" to R.drawable.icbg_fire_bolt_icon,
"https://bg3.wiki/w/images/7/7f/Command_Halt_Icon.webp" to R.drawable.icbg_command_halt_icon,
"https://bg3.wiki/w/images/0/0d/Mage_Armour_Icon.webp" to R.drawable.icbg_mage_armour_icon,
"https://bg3.wiki/w/images/1/17/Ice_Knife_Icon.webp" to R.drawable.icbg_ice_knife_icon,
"https://bg3.wiki/w/images/8/8d/Disguise_Self_Icon.webp" to R.drawable.icbg_disguise_self_icon,
"https://bg3.wiki/w/images/d/d4/Magic_Missile_Icon.webp" to R.drawable.icbg_magic_missile_icon,
"https://bg3.wiki/w/images/6/6e/Shadow_Blade_Icon.webp" to R.drawable.icbg_shadow_blade_icon,
"https://bg3.wiki/w/images/b/b9/Wild_Shape_Icon.webp" to R.drawable.icbg_wild_shape_icon,
"https://bg3.wiki/w/images/c/c3/See_Invisibility_Icon.webp" to R.drawable.icbg_see_invisibility_icon,
"https://bg3.wiki/w/images/5/50/Counterspell_Icon.webp" to R.drawable.icbg_counterspell_icon,
"https://bg3.wiki/w/images/0/0a/Unshackling_Strike_Icon.webp" to R.drawable.icbg_unshackling_strike_icon,
"https://bg3.wiki/w/images/7/7f/Remove_Curse_Icon.webp" to R.drawable.icbg_remove_curse_icon,
"https://bg3.wiki/w/images/4/46/Aberrant_Shape_Icon.webp" to R.drawable.icbg_aberrant_shape_icon,
"https://bg3.wiki/w/images/f/f6/Polymorph_Icon.webp" to R.drawable.icbg_polymorph_icon,
"https://bg3.wiki/w/images/8/8b/Dueller%27s_Enthusiasm_Icon.webp" to R.drawable.icbg_dueller_enthusiasm_icon,
"https://bg3.wiki/w/images/f/ff/Camp_Circus_B_Unfaded_Icon.webp" to R.drawable.icbg_camp_circus_b_unfaded_icon,
"https://bg3.wiki/w/images/f/f2/Camp_Aristocrat_Unfaded_Icon.webp" to R.drawable.icbg_camp_aristocrat_unfaded_icon,
"https://bg3.wiki/w/images/1/1d/Camp_Rich_C_Red_Unfaded_Icon.webp" to R.drawable.icbg_camp_rich_c_red_unfaded_icon,
"https://bg3.wiki/w/images/8/85/Strange_Chunk_of_Amber_Unfaded_Icon.png" to R.drawable.icbg_strange_chunk_of_amber_unfaded_icon,
"https://bg3.wiki/w/images/7/72/Toy_Block_A_Unfaded_Icon.png" to R.drawable.icbg_toy_block_a_unfaded_icon,
"https://bg3.wiki/w/images/f/f5/Keepsake_Locket_A_Unfaded.png" to R.drawable.icbg_keepsake_locket_a_unfaded,
"https://bg3.wiki/w/images/4/4e/Crystal_Fluorite_Shard_Unfaded.png" to R.drawable.icbg_crystal_fluorite_shard_unfaded,
"https://bg3.wiki/w/images/1/1b/Moondrop_Pendant_Unfaded.png" to R.drawable.icbg_moondrop_pendant_unfaded,
"https://bg3.wiki/w/images/4/47/Gortash%27s_Netherstone_Unfaded_Icon.png" to R.drawable.icbg_gortash_netherstone_unfaded_icon,
"https://bg3.wiki/w/images/thumb/0/0a/Unshackling_Strike_Icon.webp/144px-Unshackling_Strike_Icon.webp.png" to R.drawable.icbg_unshackling_strike,
"https://bg3.wiki/w/images/thumb/e/e9/Poisoned_Condition_Icon.webp/45px-Poisoned_Condition_Icon.webp.png" to R.drawable.icbg_poisoned_condition_icon,
"https://bg3.wiki/w/images/thumb/0/09/Paralysed_Condition_Icon.webp/45px-Paralysed_Condition_Icon.webp.png" to R.drawable.icbg_paralysed_condition_icon,
"https://bg3.wiki/w/images/thumb/8/8e/Prone_Condition_Icon.webp/38px-Prone_Condition_Icon.webp.png" to R.drawable.icbg_prone_condition_icon,
"https://bg3.wiki/w/images/thumb/6/60/Grappled_Condition_Icon.webp/45px-Grappled_Condition_Icon.webp.png" to R.drawable.icbg_grappled_condition_icon,
"https://bg3.wiki/w/images/thumb/7/70/Blinded_Condition_Icon.webp/38px-Blinded_Condition_Icon.webp.png" to R.drawable.icbg_blinded_condition_icon,
"https://bg3.wiki/w/images/thumb/4/47/Charmed_Condition_Icon.webp/38px-Charmed_Condition_Icon.webp.png" to R.drawable.icbg_charmed_condition_icon,
"https://bg3.wiki/w/images/thumb/7/75/Frightened_Condition_Icon.webp/38px-Frightened_Condition_Icon.webp.png" to R.drawable.icbg_frightened_condition_icon,
"https://bg3.wiki/w/images/thumb/2/20/Restrained_Condition_Icon.webp/45px-Restrained_Condition_Icon.webp.png" to R.drawable.icbg_restrained_condition_icon,
"https://bg3.wiki/w/images/thumb/3/32/Stunned_Condition_Icon.webp/45px-Stunned_Condition_Icon.webp.png" to R.drawable.icbg_stunned_condition_icon,
"https://bg3.wiki/w/images/thumb/9/98/Incapacitated_Condition_Icon.webp/38px-Incapacitated_Condition_Icon.webp.png" to R.drawable.icbg_incapacitated_condition_icon,
"https://bg3.wiki/w/images/thumb/a/a6/Unconscious_Condition_Icon.webp/64px-Unconscious_Condition_Icon.webp.png?20231130005748" to R.drawable.icbg_unconscious_condition_icon,
"https://bg3.wiki/w/images/thumb/d/d7/Invisible_Condition_Icon.webp/45px-Invisible_Condition_Icon.webp.png" to R.drawable.icbg_invisible_condition_icon,
"https://bg3.wiki/w/images/thumb/c/c0/Petrified_Condition_Icon.webp/45px-Petrified_Condition_Icon.webp.png" to R.drawable.icbg_petrified_condition_icon,
"https://bg3.wiki/w/images/thumb/2/2e/Background_Sage_Icon.png/180px-Background_Sage_Icon.png" to R.drawable.icbg_background_sage_icon,
"https://static.wikia.nocookie.net/witcher/images/3/3d/Tw3_weapon_repair_kit_masters.png/revision/latest?cb=20160410121438" to R.drawable.tw_weapon_repair_kit_masters,
"https://static.wikia.nocookie.net/witcher/images/c/c3/Tw3_concealment_kit.png/revision/latest?cb=20170416212412" to R.drawable.tw_concealment_kit,
"https://static.wikia.nocookie.net/forgottenrealms/images/e/ee/Forgery_Kit.png/revision/latest?cb=20231120043151" to R.drawable.tw_forgery_kit,
"https://static.wikia.nocookie.net/witcher/images/1/1d/Tw3_cloth_doll.png/revision/latest?cb=20160519181719" to R.drawable.tw_cloth_doll,
"https://static.wikia.nocookie.net/gensin-impact/images/e/ee/Item_Sentry%27s_Wooden_Whistle.png/revision/latest?cb=20240826125031" to R.drawable.tw_item_sentry_wooden_whistle,
"https://s3.eu-west-2.amazonaws.com/www.partynutters.com/img/va093-frog-toad-deluxe-venetian-mask.jpg" to R.drawable.frog_toad_deluxe_venetian_mask,
"https://drive.google.com/uc?export=view&id=15D3Jum_X_nw4tWfbaLbEvhTQQbmYCMwf" to R.drawable.ic_dice_advantage,
"https://drive.google.com/uc?export=view&id=17MrL-HAlpqPI5c6APjz5arGYrfma42on" to R.drawable.ic_drive_orc_mask,
"https://drive.google.com/uc?export=view&id=1AeSHg792EwVbkjCNjJaqzC4v0uNj9NRn" to R.drawable.ic_drive_fire_starter,
@ -247,9 +305,13 @@ object ImageCache {
"https://drive.google.com/uc?export=view&id=1hPiuG7LQc4KVqBfytAXlo0QVLpWwoKdm" to R.drawable.ic_drive_copper_coin_pile,
"https://drive.google.com/uc?export=view&id=1N73d_elaDPND3TVN2SgmBGOStSuY1aB9" to R.drawable.ic_drive_silver_coin_pile,
"https://drive.google.com/uc?export=view&id=1N-Rp4d8gTctHE5ST2qzYmT-nqoX6iyjg" to R.drawable.ic_drive_electrum_coin_pile,
"https://drive.google.com/uc?export=view&id=1sKiDdIakpe1shE0zi-Ip0CineEe1M2H2" to R.drawable.ic_drive_gold_coin_pile
"https://drive.google.com/uc?export=view&id=1sKiDdIakpe1shE0zi-Ip0CineEe1M2H2" to R.drawable.ic_drive_gold_coin_pile,
"https://drive.google.com/uc?export=view&id=1Sk2-3paCI59ET9k28HSuZaQVcGb90-A_" to R.drawable.icbg_deafened,
"https://drive.google.com/uc?export=view&id=1Amdb2gHrS6SCOa34JlQijWi0u25z62QD" to R.drawable.icbg_green_flame_sword,
)
private val missingUri = mutableSetOf<Uri>()
fun cache(uri: Uri?): ImageUio = when (uri) {
null -> {
R.drawable.icbg_generic_darkness_icon.toImage()
@ -261,8 +323,12 @@ object ImageCache {
}
else -> {
Log.v("ImageCache", "$uri")
uri.toImage()
uri.also {
if (missingUri.contains(it).not()) {
Log.v("ImageCache", "$it")
}
missingUri.add(it)
}.toImage()
}
}
}

View file

@ -37,22 +37,34 @@ val List<Alteration.Status>?.mastery: Int
}
} ?: 0
val List<Alteration.Status>?.fail: Boolean
get() = this?.any { it.fail } ?: false
val List<Alteration.Status>?.passivesBonus: Int
get() = (if (advantage) 5 else 0) - (if (disadvantage) 5 else 0)
// force a roll to hit a 20
val List<Alteration.Status>?.isCritical: Boolean
get() = this?.any { it.name == CRITIQUE } == true
// Force a roll to hit a 1
val List<Alteration.Status>?.fail: Boolean
get() = this?.any { it.fail } ?: false
// Force a roll to hit a 20
val List<Alteration.Status>?.critical: Boolean
get() = this?.any { it.critical } ?: false
// Add a dice to a critical roll.
val Map<Property, List<Alteration.Status>>.isSavageAttacks: Boolean
get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == "Attaques sauvages" } == true
get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == SAVAGE_ATTACK } == true
// Add a dice to a critical roll.
val Map<Property, List<Alteration.Status>>.isBrutalCritical: Boolean
get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == "Critique brutal" } == true
get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == BRUTAL_CRITICAL } == true
// for damage roll, previous attack roll was critical
val Map<Property, List<Alteration.Status>>.isCritical: Boolean
get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == "Critique" } == true
// for attack roll, can crit on an 19 or 20
val Map<Property, List<Alteration.Status>>.isImprovedCritical: Boolean
get() = this[Property.PHYSICAL_MELEE_ATTACK]?.any { it.name == IMPROVED_CRITICAL } == true ||
this[Property.PHYSICAL_RANGE_ATTACK]?.any { it.name == IMPROVED_CRITICAL } == true
private const val CRITIQUE = "Critique"
private const val SAVAGE_ATTACK = "Attaques sauvages"
private const val BRUTAL_CRITICAL = "Critique brutal"
private const val IMPROVED_CRITICAL = "Critique amélioré"

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -6,10 +6,6 @@ pluginManagement {
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0"
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {