Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nextgen)(in progress): added ability to limit amount of different items #3784

Draft
wants to merge 2 commits into
base: nextgen
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src-theme/src/integration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface GroupedModules {

export type ModuleSetting =
BlocksSetting
| ItemsSetting
| KeySetting
| BooleanSetting
| FloatSetting
Expand All @@ -35,6 +36,12 @@ export interface BlocksSetting {
value: string[];
}

export interface ItemsSetting {
valueType: string;
name: string;
value: string;
}

export interface TextSetting {
valueType: string;
name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import ColorSetting from "../ColorSetting.svelte";
import TextSetting from "../TextSetting.svelte";
import KeySetting from "../KeySetting.svelte";
import ItemsSetting from "../items/ItemSetting.svelte";
import BlocksSetting from "../blocks/BlocksSetting.svelte";
import {slide} from "svelte/transition";
import {onMount} from "svelte";
Expand Down Expand Up @@ -56,6 +57,8 @@
<TextSetting bind:setting={setting} on:change/>
{:else if setting.valueType === "KEY"}
<KeySetting bind:setting={setting} on:change/>
{:else if setting.valueType === "ITEMS"}
<ItemsSetting bind:setting={setting} on:change/>
{:else if setting.valueType === "BLOCKS"}
<BlocksSetting bind:setting={setting} on:change/>
{:else if setting.valueType === "TEXT_ARRAY"}
Expand All @@ -64,4 +67,4 @@
<div style="color: white">Unsupported setting {setting.valueType}</div>
{/if}
</div>
{/if}
{/if}
52 changes: 52 additions & 0 deletions src-theme/src/routes/clickgui/setting/items/Item.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script lang="ts">
import {REST_BASE} from "../../../../integration/host";
import {createEventDispatcher} from "svelte";

const dispatch = createEventDispatcher<{
toggle: { identifier: string, enabled: boolean }
}>();

export let identifier: string;
export let name: string;
export let enabled: boolean;
</script>

<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="item" on:click={() => dispatch("toggle", {enabled: !enabled, identifier})}>
<img class="icon" src="{REST_BASE}/api/v1/client/resource/itemTexture?id={identifier}" alt={identifier}/>
<div class="name">{name}</div>
<div class="tick">
{#if enabled}
<img src="img/clickgui/icon-tick-checked.svg" alt="enabled">
{:else}
<img src="img/clickgui/icon-tick.svg" alt="disabled">
{/if}
</div>
</div>

<style lang="scss">
@import "../../../../colors.scss";

.item {
display: grid;
grid-template-columns: max-content 1fr max-content;
align-items: center;
column-gap: 5px;
cursor: pointer;
margin: 2px 5px 2px 0;
}

.icon {
height: 25px;
width: 25px;
}

.name {
font-size: 12px;
color: $clickgui-text-color;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
</style>
93 changes: 93 additions & 0 deletions src-theme/src/routes/clickgui/setting/items/ItemSetting.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<script lang="ts">
import {createEventDispatcher, onMount} from "svelte";
import type {ItemsSetting, ModuleSetting} from "../../../../integration/types";
import {getRegistries} from "../../../../integration/rest";
import Item from "./Item.svelte";
import VirtualList from "./VirtualList.svelte";
import {convertToSpacedString, spaceSeperatedNames} from "../../../../theme/theme_config";

export let setting: ModuleSetting;

const cSetting = setting as ItemsSetting;

interface Item {
name: string;
identifier: string;
}

const dispatch = createEventDispatcher();
let items: Item[] = [];
let renderedItems: Item[] = items;
let searchQuery = "";

$: {
let filteredItems = items;
if (searchQuery) {
filteredItems = filteredItems.filter(b => b.name.toLowerCase().includes(searchQuery.toLowerCase()));
}
renderedItems = filteredItems;
}

onMount(async () => {
let i = (await getRegistries()).items;

if (i !== undefined) {
items = i.sort((a, b) => a.identifier.localeCompare(b.identifier));
}
});

function handleItemToggle(e: CustomEvent<{ identifier: string, enabled: boolean }>) {
console.log(e);
if (e.detail.enabled) {
cSetting.value = [...cSetting.value, e.detail.identifier];
} else {
cSetting.value = cSetting.value.filter(b => b !== e.detail.identifier);
}

setting = { ...cSetting };
dispatch("change");
}
</script>

<div class="setting">
<div class="name">{$spaceSeperatedNames ? convertToSpacedString(cSetting.name) : cSetting.name}</div>
<input type="text" placeholder="Search" class="search-input" bind:value={searchQuery} spellcheck="false">
<div class="results">
<VirtualList items={renderedItems} let:item>
<Item identifier={item.identifier} name={item.name} enabled={cSetting.value.includes(item.identifier)} on:toggle={handleItemToggle}/>
</VirtualList>
</div>
</div>

<style lang="scss">
@import "../../../../colors.scss";

.setting {
padding: 7px 0;
}

.results {
height: 200px;
overflow-y: auto;
overflow-x: hidden;
}

.name {
color: $clickgui-text-color;
font-size: 12px;
font-weight: 500;
margin-bottom: 5px;
}

.search-input {
width: 100%;
border: none;
border-bottom: solid 1px $accent-color;
font-family: "Inter", sans-serif;
font-size: 12px;
padding: 5px;
color: $clickgui-text-color;
margin-bottom: 5px;
background-color: rgba($clickgui-base-color, .36);
}
</style>
137 changes: 137 additions & 0 deletions src-theme/src/routes/clickgui/setting/items/VirtualList.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<!-- Adapted from https://github.com/sveltejs/svelte-virtual-list -->
<script lang="js">
import {onMount, tick} from 'svelte';
// props
export let items;
export let height = '100%';
export let itemHeight = undefined;
// read-only, but visible to consumers via bind:start
export let start = 0;
export let end = 0;
// local state
let height_map = [];
let rows;
let viewport;
let contents;
let viewport_height = 0;
let visible;
let mounted;
let top = 0;
let bottom = 0;
let average_height;
$: visible = items.slice(start, end).map((data, i) => {
return { index: i + start, data };
});
// whenever `items` changes, invalidate the current heightmap
$: if (mounted) refresh(items, viewport_height, itemHeight);
async function refresh(items, viewport_height, itemHeight) {
const { scrollTop } = viewport;
await tick(); // wait until the DOM is up to date
let content_height = top - scrollTop;
let i = start;
while (content_height < viewport_height && i < items.length) {
let row = rows[i - start];
if (!row) {
end = i + 1;
await tick(); // render the newly visible row
row = rows[i - start];
}
const row_height = height_map[i] = itemHeight || row.offsetHeight;
content_height += row_height;
i += 1;
}
end = i;
const remaining = items.length - end;
average_height = (top + content_height) / end;
bottom = remaining * average_height;
height_map.length = items.length;

setTimeout(() => {
viewport.scrollTop = 0;
}, 100);
}
async function handle_scroll() {
const { scrollTop } = viewport;
const old_start = start;
for (let v = 0; v < rows.length; v += 1) {
height_map[start + v] = itemHeight || rows[v].offsetHeight;
}
let i = 0;
let y = 0;
while (i < items.length) {
const row_height = height_map[i] || average_height;
if (y + row_height > scrollTop) {
start = i;
top = y;
break;
}
y += row_height;
i += 1;
}
while (i < items.length) {
y += height_map[i] || average_height;
i += 1;
if (y > scrollTop + viewport_height) break;
}
end = i;
const remaining = items.length - end;
average_height = y / end;
while (i < items.length) height_map[i++] = average_height;
bottom = remaining * average_height;
// prevent jumping if we scrolled up into unknown territory
if (start < old_start) {
await tick();
let expected_height = 0;
let actual_height = 0;
for (let i = start; i < old_start; i +=1) {
if (rows[i - start]) {
expected_height += height_map[i];
actual_height += itemHeight || rows[i - start].offsetHeight;
}
}
const d = actual_height - expected_height;
viewport.scrollTo(0, scrollTop + d);
}
// TODO if we overestimated the space these
// rows would occupy we may need to add some
// more. maybe we can just call handle_scroll again?
}
// trigger initial refresh
onMount(() => {
rows = contents.getElementsByTagName('svelte-virtual-list-row');
mounted = true;
});
</script>

<style>
svelte-virtual-list-viewport {
position: relative;
overflow-y: auto;
-webkit-overflow-scrolling:touch;
display: block;
}
svelte-virtual-list-contents, svelte-virtual-list-row {
display: block;
}
svelte-virtual-list-row {
overflow: hidden;
}
</style>

<svelte-virtual-list-viewport
bind:this={viewport}
bind:offsetHeight={viewport_height}
on:scroll={handle_scroll}
style="height: {height};"
>
<svelte-virtual-list-contents
bind:this={contents}
style="padding-top: {top}px; padding-bottom: {bottom}px;"
>
{#each visible as row (row.index)}
<svelte-virtual-list-row>
<slot item={row.data}>Missing template</slot>
</svelte-virtual-list-row>
{/each}
</svelte-virtual-list-contents>
</svelte-virtual-list-viewport>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
package net.ccbluex.liquidbounce.features.module.modules.player.invcleaner

import net.ccbluex.liquidbounce.features.module.modules.player.invcleaner.items.ItemFacet
import net.ccbluex.liquidbounce.utils.client.logger
import net.ccbluex.liquidbounce.utils.item.isNothing
import net.minecraft.item.Item

class CleanupPlanGenerator(
private val template: CleanupPlanPlacementTemplate,
Expand Down Expand Up @@ -58,6 +60,18 @@ class CleanupPlanGenerator(
processItemCategory(category, availableItems)
}

availableItems.forEach { slot ->
val limit = template.itemLimitPerItem[slot.itemStack.item] ?: Int.MAX_VALUE
var itemCount = packer.usefulItems.count { it.itemStack.item == slot.itemStack.item }
packer.usefulItems.removeIf {
if (itemCount > limit) {
logger.info("removed item from useful items list")
itemCount--
return@removeIf true
}
false
}
}
Comment on lines +63 to +74
Copy link
Contributor

@superblaubeere27 superblaubeere27 Aug 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty hacky and doesn't really integrate with the rest of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty hacky and doesn't really integrate with the rest of the code.

I agree, I will refactor this later

// We aren't allowed to touch those, so we just consider them as useful.
packer.usefulItems.addAll(this.template.forbiddenSlots)

Expand All @@ -74,7 +88,7 @@ class CleanupPlanGenerator(
) {
val maxItemCount =
if (category.type.oneIsSufficient) {
if (this.packer.alreadyProviededFunctions.contains(category.type.providedFunction)) 0 else 1
if (this.packer.alreadyProvidedFunctions.contains(category.type.providedFunction)) 0 else 1
} else {
template.itemLimitPerCategory[category] ?: Int.MAX_VALUE
}
Expand Down Expand Up @@ -132,6 +146,7 @@ class CleanupPlanPlacementTemplate(
* If an item is not in this map, there is no limit.
*/
val itemLimitPerCategory: Map<ItemCategory, Int>,
val itemLimitPerItem: Map<Item, Int>,
/**
* If false, slots which also contains items of that category, those items are not replaced with other items.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ItemPacker {
*/
val usefulItems = HashSet<ItemSlot>()

val alreadyProviededFunctions = HashSet<ItemFunction>()
val alreadyProvidedFunctions = HashSet<ItemFunction>()

/**
* Takes items from the [itemsToFillIn] list until it collected [maxItemCount] items is and [requiredStackCount]
Expand Down Expand Up @@ -61,7 +61,7 @@ class ItemPacker {

usefulItems.add(filledInItemSlot)

alreadyProviededFunctions.addAll(filledInItem.providedItemFunctions)
alreadyProvidedFunctions.addAll(filledInItem.providedItemFunctions)

currentItemCount += filledInItem.itemStack.count
currentStackCount++
Expand Down
Loading
Loading